How To Get Value From Ng-template With Typescript
ngTemplateOutlet is a powerful tool for creating customisable components. It is used by many Athwart libraries to enable users to provide custom templates. But how can we do this for our own components?
In this article we demonstrate how to use ngTemplateOutlet, along with ngTemplateOutletContext, to make a component completely customisable.
Customising a Dropdown Selector
We will be working with a dropdown selector as information technology serves equally a swell use case for customising a component with ngTemplateOutlet . Our dropdown selector is used by multiple clients (my two shark obsessed children) who each take a number of outstanding feature requests. Let's first by introducing our component code and then commencement adding new features.
(If you want to skip to the end, the final customisable selector is live here).
Selector Component
Our selector begins with a make clean api. It takes a list of strings and displays those via ngFor in a dropdown.
<> Copy
export class SelectorComponent { selected: string ; @ Input ( ) options: cord [ ] ; @ Output ( ) selectionChanged = new EventEmitter < cord > ( ) ; selectOption (option: string ) { this . selected = option; this . selectionChanged . emit (option) ; } } // selector.component.ts
All looks good so far with a clean component interface.
<> Copy
<div dropdown > <button dropdownToggle > {{selected || 'Select'}} </button > <ul dropdownMenu > <li *ngFor = "let pick of options" (click) = "selectOption(option)" > {{option}} </li > </ul > </div > <!-- selector.component.html -->
Using this component our first customer can select their favourite shark.
<> Re-create
<app-selector [options] = "sharks" > </app-selector > <!-- client-one.component.html -->
Feature: Customise Pick Text
Our second client, who also likes sharks, wants to include the Latin name in their dropdown menu. We could make a small change by adding a brandish phone call-back function as an Input to update the displayed text. This is non necessarily recommended.
<> Re-create
@ Input ( ) displayFunc : ( string ) => string = x => ten; // selector.component.ts
<> Copy
<li *ngFor = "let option of options" > <!-- Pass the option through the display callback --> {{displayFunc(choice)}} </li > <!-- selector.component.html -->
<> Copy
<app-selector [options] = "sharks" [displayFunc] = "appendLatin" > </app-selector > <!-- customer-two.component.html -->
Reminder: This is not the recommended approach
Feature: Safe To Swim Icon
Client one at present wants to include an icon depicting whether a shark is condom to swim with. They provide the states with their hefty icon package which we take to use. How are we going to support this?
Unlike the previous request, which merely changed the text content, calculation in an icon will require structural changes to our HTML template.
Wrong arroyo using *ngIf
Without knowing well-nigh ngTemplateOutlet nosotros could decide to use *ngIf and some other callback that provides the icon name based on the current shark.
<> Copy
<li *ngFor = "permit option of options" > <!-- Introducing the icon into our selector --> <c1-icon *ngIf = "getIconFunc(option)" [name] = "getIconFunc(option)" /> {{displayFunc(choice)}} </li > <!-- selector.component.html -->
If no icon callback is provided the default returns undefined to hide the icon via our ngIf. This ensures that our other clients do not encounter these icons.
<> Copy
@ Input ( ) getIconFunc : ( cord ) => string = x => undefined ; // selector.component.ts
<> Copy
<app-selector [options] = "sharks" [getIconFunc] = "getIconFunc" > </app-selector > <!-- client-one.component.html -->
This works and enables them to have the post-obit selector but allow's not get also excited because this was not a great solution.
Unhappy Client due to Icon dependency
In the previous feature request we introduced a dependency on client 1's icon bundle. This is really bad! Consider forcing other clients to install an actress dependency to compile their applications fifty-fifty though they will never actually require the parcel.
You lot may consider your all-time option is to fork the component and have a separate instance for each client. While this may be a quick set up for client ii, it now ways you take multiple dropdown selectors to back up. Not a happy position as a developer!
What about using ng-content?
In Athwart we can use <ng-content> to perform content project. Possibly we could replace the icon in the template with a <ng-content> and take client one project their icon into our selector. This way we can remove the icon dependency from our component.
<> Copy
<li *ngFor = "permit selection of options" > <!-- Removed: <c1-icon [name]="swimIcon(option)" /> --> <ng-content > </ng-content > {{displayFunc(option)}} </li > <!-- selector.component.html -->
<> Copy
<app-selector [options] = "sharks" > <c1-icon [name] = "swimIcon(????)" /> </app-selector > <!-- client-one.component.html -->
While this looks promising it will not work. The icon volition merely be displayed for the terminal item in the list. You can only project content into a unmarried location unless you use named slots. In that location is no easy way to dynamically proper name slots like in our list in a higher place.
The principal issue is that <ng-content> is not aware of the context where information technology is being rendered. Information technology does not know the shark option it is being used for. This means that we cannot customise its content based on the dropdown value.
If merely there was a way for united states of america to project a template into our component that was also aware of its local context. This is where ngTemplateOutlet comes in!
NgTemplateOutlet
ngTemplateOutlet acts every bit a placeholder to render a template afterward providing that template with context. In our case we desire a template placeholder for each dropdown option and the context would be the shark.
The Athwart documentation for ngTemplateOutlet is currently a little lacking. This issue has been raised and ideas on how to demonstrate the feature take started beingness shared.
Defining a Template
Before nosotros tin can apply ngTemplateOutlet we must beginning define a template using <ng-template>. The template is the body of the <ng-template> chemical element.
<> Copy
<ng-template #myTemplate > <div > Hi template </div > </ng-template >
To reference the template we name it via # syntax. By adding #myTemplate to the element nosotros tin can get a reference to the template using the name myTemplate . The type of myTemplate is TemplateRef.
Rendering a Template
The content of a <ng-Template> chemical element is non rendered in the browser. To accept the template body rendered we must now pass the template reference to a ngTemplateOutlet.
<> Copy
<!-- Ascertain our template --> <ng-template #myTemplate > World! </ng-template > Hello <!-- Render the template in this outlet --> <ng-container [ngTemplateOutlet] = "myTemplate" > </ng-container >
ng-template and ngTemplateOutlet enable united states of america to define re-usable templates which in itself is a powerful feature but we are simply getting started!
Supplying the Template Context
Nosotros can take templates to the next level by supplying a context. This enables us to pass information to the template. In our example the data is the shark for the current option. To pass context to a template you lot utilise [ngTemplateOutletContext].
Hither we are passing each dropdown option to the optionTemplate. This volition enable the option template to display a different value for each item in the list. Nosotros are also setting the current index to the idx belongings of our context as this tin can exist useful for styling.
<> Copy
<li *ngFor = "let particular of items; index every bit i" > <!-- Setting the option as the $implicit belongings of our context along with the row alphabetize --> <ng-container [ngTemplateOutlet] = "optionTemplate" [ngTemplateOutletContext] = "{ $implicit: option, idx: i }" > </ng-container > </li > <!-- selector.component.html -->
You can besides use the abbreviated syntax below.
<> Copy
<!-- Culling syntax --> <ng-container *ngTemplateOutlet = "optionTemplate; context:{ $implicit: option, idx: i }" > </ng-container >
Using the Context in your template
To admission the context in our template we apply permit-* syntax to ascertain template input variables. To bind the $implicit property to a template variable chosen pick we add let-pick to our template. We can apply any name for our template variable so let-particular or let-shark would as well bind to the $implicit holding in the context.
This enables us to define a template outside of the selector component simply with admission to current option just equally if our template was defined in the dropdown itself!
<> Copy
<ng-template #optionTemplate let-option let-position = "idx" > {{ position }} : {{option}} </ng-template > <!-- client-one.component.html -->
To access the other properties on our context we have to exist more explicit. To bind the idx value to a template variable called position we add let-position=idx. Alternatively nosotros could name it id by adding let-id=idx.
Note that we must know the verbal property name when extracting values from the context that are non the $implicit property. The $implicit property is a handy tool which means users exercise not have to be aware of this proper name equally well as having to write less lawmaking.
Library authors please add the blazon structure of your context to your documentation! There is currently no machine-complete / type checking available for template input variables.
Past using template input variables we are able to combine state from where we ascertain the template, with the context provided to us where the template is instantiated. This provides us with some amazing capabilities!
Solving our feature requests
Nosotros are at present in a position to solve our clashing client demands with a single selector. As a reminder, our first customer wanted their custom icon in the dropdown while our second customer, justifiably, did non want that dependency.
Setup the template outlet in our component
To use a template within our app-selector nosotros replace the display function and icon chemical element with a ng-container containing a ngTemplateOutlet . This outlet will either apply the user's optionTemplate or our defaultTemplate if no template is provided by the user.
<> Copy
<li *ngFor = "let option of options; alphabetize as i" > <!-- Define a default template --> <ng-template #defaultTemplate permit-selection > {{ option }} </ng-template > <ng-container [ngTemplateOutlet] = "optionTemplate || defaultTemplate" [ngTemplateOutletContext] = "{ $implicit: choice, alphabetize: i}" > </ng-container > </li > <!-- selector.component.html -->
Default templates are a smashing way to retro-fit ngTemplateOutlet to an existing component. To ensure that the template volition exist able to display the current option we must retrieve to setup the context. We ready the choice to be the $implicit property and also provide the electric current row index.
The component will have the optionTemplate via an Input.
<> Re-create
@ Input ( ) optionTemplate: TemplateRef < any > ; // selector.component.ts
You can also employ@ContentChildto pass the template into your component. This forces the template to be divers within the<app-selector>which may be preferred if y'all have a lot of Input properties. Notwithstanding, this does arrive harder to share templates beyond multiple component instances. Run into this [Stackblitz](https://stackblitz.com/edit/ngtemplateoutletcontext?file=src%2Fapp%2Fmy-selector%2Fmy-selector.component.ts) for an example.
Define the client template
Now we can define our custom template in client one's codebase. Here we use the template input variable to ensure we brandish the right icon for the given shark.
<> Copy
<ng-template #sharkTemplate let-shark > <c1-icon proper noun = "{{ getIconFunc(shark) }}" /> {{ shark }} </ng-template > <!-- Pass sharkTemplate to our selector via an Input --> <app-selector [options] = "sharks" [optionTemplate] = "sharkTemplate" > </app-selector > <!-- client-one.component.html -->
We then laissez passer our template by reference into the component via the optionTemplate @Input.
This all results in our final shark selector coming together client one'due south requests and at the same time ensuring no other clients require the icon dependency any more than.
Tractors instead of sharks
Just when we thought nosotros were finished client two comes back to usa with the exciting news that they don't like sharks anymore and instead they dearest tractors! They now want a dropdown to pick tractors with pictures and buttons.
The not bad thing is that we can give them whatever they want without irresolute any of our selector code. This is the beauty and ability of ngTemplateOutlet.
We just update the template use in client two's lawmaking base for tractors and pass that in.
<> Copy
<ng-template #tractorTemplate permit-tractor > <label > {{ tractor.name }} </characterization > <img src = "{{ tractor.img }}" /> <push button > Buy Now! </button > </ng-template > <!-- No change to selector for make new dropdown way --> <app-selector [options] = "tractors" [optionTemplate] = "tractorTemplate" > </app-selector > <!-- customer-two.component.html -->
Final Selector Code
By using ngTemplateOutlet we are able to seperate the work of being a selector from the user customisations. This enables us to maintain a minimal component api without restricting our clients' inventiveness.
<> Re-create
export class SelectorComponent < T > { @ Input ( ) options: T [ ] ; @ Input ( "optionTemplate" ) optionTemplateRef? : TemplateRef < any > ; @ Output ( ) selectionChanged = new EventEmitter < T > ( ) ; } // selector.component.ts
<> Copy
<li *ngFor = "let pick of options; index as i" > <ng-template #defaultTemplate permit-option > {{ option }} </ng-template > <ng-container [ngTemplateOutlet] = "optionTemplate || defaultTemplate" [ngTemplateOutletContext] = "{ $implicit: option, alphabetize: i}" > </ng-container > </li > <!-- selector.component.html -->
Conclusion
I promise that after reading this article you will be able to utilise ngTemplateOutlet to back up template customisations in your own components! I also promise you will have a deeper agreement of how your favourite component libraries are using ngTemplateOutlet to enable you to customise them.
Further Reading
Hither I take covered a single employ example for ngTemplateOutlet. If you liked this article then I would strongly recommend reading Alex Inkin'south article Agnostic components in Angular which takes things fifty-fifty further.
Live Example
Experiment for yourself with this live example on Stackblitz or clone the repo StephenCooper/ngTemplateOutlets from GitHub.
If you prefer watching videos y'all can come across me present this article at Athwart Connect 2019.
How To Get Value From Ng-template With Typescript,
Source: https://indepth.dev/posts/1405/ngtemplateoutlet
Posted by: caponecestion.blogspot.com

0 Response to "How To Get Value From Ng-template With Typescript"
Post a Comment