Reusable Component Pieces with ngTemplateOutlet

tldr;

ng-template, ng-container, and ngTemplateOutlet provide the ability to reuse content inside an Angular component. Understanding what’s going on and how to use these methods allows a developer to build what they want without needing to duplicate parts of the template.

Explanation of the Problem

Have you ever needed to use *ngIf in your template, and if the expression evaluates to false use a backup template? On top of that, maybe both situations (if the expression is true or false) require the same layout inside of the element that has the *ngIf directive on it.

I recently came across this situation. We had some data that was being output in a list either had a link that stayed internal to the app or opened a link in a new tab external to the application. The content inside the links, however, always had the same layout. I didn’t want to duplicate the inner content, and luckily Angular provides the ability to accomplish this. The method includes using ng-template, ng-container, and ngTemplateOutlet.

Before getting to the end solution, let’s look at an example of the data and how it’s output without using ng-template.

export class ListComponent {
links = [
{ internal: true, display: “Home”, url: “/” },
{ internal: false, display: “External”, url: “<https://test.com>” },
];
}<ul>
<li *ngFor=”let link of links”>
<a *ngIf=”link.internal” [routerLink]=”link.url”>
<img src=”/assets/icon.svg” alt=””>
<span>{{ link.display }}</span>
</a>
<a *ngIf=”!link.internal” [attr.href]=”link.url”>
<img src=”/assets/icon.svg” alt=””>
<span>{{ link.display }}</span>
</a>
</li>
</ul>

In this method, we have two *ngIf directives on two separate a tags, and the inner content of the link is the same in both cases. If that inner content changes, we have to make the change to both places or else there are inconsistencies in the layout. This is not ideal; we are repeating ourselves and there are multiple locations where we can introduce bugs.

Solution

Angular provides a much more elegant solution to our problem. Our TypeScript code from above will remain the same, but we can have a lot less duplication in the HTML by using a couple special Angular tags. First I’ll show the HTML, and then we’ll discuss the different pieces.

<ul>
<li *ngFor=”let link of links”>
<a *ngIf=”link.internal; else externalLink” [routerLink]=”link.url”>
<ng-container
[ngTemplateOutlet]=”linkLayout”
[ngTemplateOutletContext]=”{ link: link }”
></ng-container>
</a>
<ng-template #externalLink>
<a [attr.href]=”link.url”>
<ng-container
[ngTemplateOutlet]=”linkLayout”
[ngTemplateOutletContext]=”{ link: link }”
></ng-container>
</a>
</ng-template>
</li>
</ul><ng-template #linkLayout let-link=”link”>
<img src=”/assets/icon.svg” alt=”” />
<span>{{ link.display }}</span>
</ng-template>

There’s a lot going on here, so let’s break it down. We’ll start with the *ngIf on the a tag.

<a *ngIf=”link.internal; else externalLink” [routerLink]=”link.url”>
<ng-container
[ngTemplateOutlet]=”linkLayout”
[ngTemplateOutletContext]=”{ link: link }”
></ng-container>
</a>
<ng-template #externalLink>
<a [attr.href]=”link.url”>
<ng-container
[ngTemplateOutlet]=”linkLayout”
[ngTemplateOutletContext]=”{ link: link }”
></ng-container>
</a>
</ng-template>

The *ngIf directive has an expression listed: if link.internal evaluates to true, then show this a tag. If it’s false, output the ng-template tag that’s referenced by the externalLink local template variable. This is how we can either navigate to a new route that’s part of the Angular app or external to the app. This is a method that can be used in any situation where your *ngIf needs an else clause.

Next up, let’s look at what’s going on with the ng-container inside both of the a tags in the example.

<ng-container
[ngTemplateOutlet]=”linkLayout”
[ngTemplateOutletContext]=”{ link: link }”
></ng-container>

You can read more about the ng-container element in the Angular docs, but at a high level it’s a special element that can hold structural directives without adding new elements to the DOM. It can also be used in situations like this, with the ngTemplateOutlet structural directive. The ngTemplateOutlet directive determines what ng-template will be output on the page inside the ng-container. The context for the template, or needed variables, can be declared with the ngTemplateOutletContext directive. In the above case, we are saying that the #linkLayout content should be output on the page, and that there should be a variable called link that will be used inside the ng-template. The contents of the ng-template tag can be placed anywhere in the HTML file, though I’ve placed them at the bottom in this example.

<ng-template #linkLayout let-link=”link”>
<img src=”/assets/icon.svg” alt=”” />
<span>{{ link.display }}</span>
</ng-template>

This template will be output on the screen anywhere it’s referenced. In this case, inside both a tags. If we want to change the layout inside the a tags, we need only change this one spot in the HTML. Everything will be kept in sync, and we have fewer possibilities of inconsistencies in the component.

There is one alternate way of declaring the ngTemplateOutlet. The result will be the same as the method shown above.

<ng-container
*ngTemplateOutlet=”linkLayout; context: { link: link }”
></ng-container>

Conclusion

The above method limits the duplication to a certain degree, especially for the content inside the a tag. The example here is a fairly simple layout. This method becomes more useful when the internal layout becomes more complicated. That inner layout is defined once and reused in multiple locations. You could create a new component if you wanted, but that can introduce complexity sometimes as well.

Knowing how to use ng-template, ng-container, and ngTemplateOutlet can allow you to effectively output the content of a component with minimal duplication.

Reusable Component Pieces with ngTemplateOutlet was originally published in ngconf on Medium, where people are continuing the conversation by highlighting and responding to this story.

Leave a Comment

Your email address will not be published.