DEV Community

Frederik Prijck
Frederik Prijck

Posted on

Reusable HTML in Angular using NgTemplateOutlet

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Using this template can be done by using the ngTemplateOutlet directive on an ng-container component.

    <ng-container [ngTemplateOutlet]="itemTemplate">
    </ng-container>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Note: The ngTemplateOutlet directive can also be used as a structural directive:

<ng-container *ngTemplateOutlet="itemTemplate; context: { $implicit: item }">
</ng-container>
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
sureshkumar84 profile image
sureshkumar84

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.

Collapse
 
scooperdev profile image
Stephen Cooper

I had a similar requirement. Here is an example of how you can do it.

stackblitz.com/edit/ngtemplateoutl...

Collapse
 
websamurai profile image
Web samurai

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

Collapse
 
ovidiu141 profile image
Ovidiu Miu

Is using ngTemplateOutlet for html code reuse officially recommended?

Collapse
 
twittwer profile image
Tobias Wittwer

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 of item.name.

Collapse
 
frederikprijck profile image
Frederik Prijck

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.

Collapse
 
layzee profile image
Lars Gyrup Brink Nielsen

Good introduction! Thanks for writing it, Frederik.

Collapse
 
frederikprijck profile image
Frederik Prijck

Thanks, glad you like it!

Collapse
 
ra1da35ma profile image
Rasheed Rahman

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)

Collapse
 
websamurai profile image
Web samurai

very nice explanation.
I will definitely use the in my project. thanks for sharing