When creating an application, you might find yourself reusing the same HTML. One way to get rid of this could be to make use of components. Even tho I love using components for this, Angular allows us to reuse HTML without introducing components as well.
Recently I find myself using this as I wanted to limit the amount of refactoring in the current PR (so I didn't want to move parts of the HTML and code into a component) while I still wanted to introduce reusability in the HTML.
Take a look at the following example:
<h1>Movies</h1>
<div *ngFor="let movie of movies">
<span>{{ movie.name }}</span>
<p>{{ movie.description }}</p>
<button (click)="onClick(movie)">Click Me</button>
</div>
<h1>Favorite Movies</h1>
<div *ngFor="let movie of favoriteMovies">
<span>{{ movie.name }}</span>
<p>{{ movie.description }}</p>
<button (click)="onClick(movie)">Click Me</button>
</div>
As mentioned, to limit duplication and introduce reusability we could move the content of our ngFor
into its own component, add the appropriate input and outputs and set up the bindings appropriately.
Apart from using a Component, we can also make use of Angular's NgTemplateOutlet. I'll generally recommend using a component. However, if you find your self in a situation where the HTML you wanna reuse is so big and bound to the current component in so many ways, using NgTemplateOutlet might be a first step into making the HTML reusable. Another reason to use NgTemplateOutlet could also be when the HTML is so simple, that introducing a separate component might not be making things easier to grasp.
<ng-template #itemTemplate>
<span>{{ item.name }}</span>
<p>{{ item.description }}</p>
<button (click)="onClick(item)">Click Me</button>
</ng-template>
Moving the content of our ngFor
into an ng-template
is pretty straightforward. However, as we want to render this template several times, each time for a different item, we'll need to declare some kind of context for the ng-template so that angular knows how to bind the data. In our case, we'll be adding let-item
to the ng-template.
<ng-template #itemTemplate let-item>
<span>{{ item.name }}</span>
<p>{{ item.description }}</p>
<button (click)="onClick(item)">Click Me</button>
</ng-template>
Using this template can be done by using the ngTemplateOutlet
directive on an ng-container
component.
<ng-container [ngTemplateOutlet]="itemTemplate">
</ng-container>
We can add a context object to ngTemplateOutlet which allows us to provide it with a value to use as the item we have defined on our ng-template.
A context object should be an object, the object's keys will be available for binding by the local template let declarations. Using the key $implicit in the context object will set its value as default.
What this means is that we can either use let-item="movie"
and provide the context as an object containing a movie
property: [ngTemplateOutletContext]="{ movie: movie }"
or use let-item
and [ngTemplateOutletContext]="{ $implicit: movie }"
. Both of them will make the movie available as item
inside the template.
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: movie }">
</ng-container>
Putting everything together, we now should be able to refactor our HTML to look like this:
<h1>Movies</h1>
<div *ngFor="let movie of movies">
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: movie }">
</ng-container>
</div>
<h1>Favorite Movies</h1>
<div *ngFor="let movie of favoriteMovies">
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: movie }">
</ng-container>
</div>
<ng-template let-item>
<span>{{ item.name }}</span>
<p>{{ item.description }}</p>
<button (click)="onClick(item)">Click Me</button>
</ng-template>
We can even add another ng-template to reuse the list. For demonstration purposes, we'll not use the $implicit
property on the NgTemplateOutlet's context in this case. Instead, we'll explicitly assign our value to a property, other than $implicit, and use that same property in let-list="list"
, where the right side of the assignment refers to the context property while the left side is defining a variable usable in the ng-template.
<h1>Movies</h1>
<ng-container [ngTemplateOutlet]="listTemplate" [ngTemplateOutletContext]="{ list: movies }">
</ng-container>
<h1>Favorite Movies</h1>
<ng-container [ngTemplateOutlet]="listTemplate" [ngTemplateOutletContext]="{ list: favoriteMovies }">
</ng-container>
<ng-template #listTemplate let-list="list">
<div *ngFor="let item of list">
<ng-container [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item }">
</ng-container>
</div>
</ng-template>
<ng-template #itemTemplate let-item>
<span>{{ item.name }}</span>
<p>{{ item.description }}</p>
<button (click)="onClick(item)">Click Me</button>
</ng-template>
Note: The ngTemplateOutlet directive can also be used as a structural directive:
<ng-container *ngTemplateOutlet="itemTemplate; context: { $implicit: item }">
</ng-container>
Conclusion
Even tho using Components is still the most ideal way in Angular to introduce reusable blocks, Angular's ngTemplateOutlet allows for a way to make use of reusable blocks of HTML without introducing new components.
You can find a running example at: https://stackblitz.com/edit/angular-fkqhbz
Top comments (10)
How to use the same idea in parent (ng-template), child(ngTemplateOutlet) with extends. Because I have a scenario like reuse the template of the parent in child component.
Parent-Component:
Child-Comonent:
Thanks in advance.
I had a similar requirement. Here is an example of how you can do it.
stackblitz.com/edit/ngtemplateoutl...
I click one solution for redundant templates through out the application.
can we create the templates Dicitonary i mean some how and maintain the statically through the application and use just pick from the dictionary by key.
whats your thoughts on this
Is using
ngTemplateOutlet
for html code reuse officially recommended?Really like the approach, do you know a way to accomplish type safety in the template?
To enable autocomplete / Ensure catching of typos like
item.Name
instead ofitem.name
.Hey Tobias, I don't think this is possible ... I'd argue if it gets to complex you might want to move to a component anyway.
Good introduction! Thanks for writing it, Frederik.
Thanks, glad you like it!
so with ng-template, the block is not in the DOM right,
how can I make it in the DOM but hidden, then have it re-used in diff views.
More like a desktop & (mobile view with tabs)
very nice explanation.
I will definitely use the in my project. thanks for sharing