Angular NgTemplateOutlet directive is a structural directive used for dynamically instantiating a template by its template reference passing a context object. NgTemplateOutlet is used to write reusable templates. Moreover, we can dynamically render these templates at multiple locations in our project.
By the time you may be confused with what does it means to instantiate a template and what the heck is this a context object. Don’t worry I am going to explain all these terms step by step with detailed use case examples.
How to use NgTemplateOutlet Directive?
There are two elements required to use NgTemplateOutlet Directive, these are ng-template and ng-container. Write your HTML template in an ng-template tag with a template reference and use this template reference to instantiate NgtemplateOutlet in an ng-container. Look at the example below.
<ng-template #tempRef>
This is a simple template
</ng-template>
<ng-container *ngTemplateOutlet="tempRef"></ng-container>
Here #tempRef is a reference variable for the template. We passed this reference variable to the *ngTemplateOutlet directive to create an instance of the above template written in <ng-template>….</ng-template> block. Therefore the above template is rendered in the ng-container block.
You can repeat the code <ng-container *ngTemplateOutlet="tempRef"></ng-container>
as many times as you want to render the above template. So what’s the benefit of doing all this? We could have simply used other HTML tags like <div> or <p> tag to write the contents which we wrote in ng-template. Why we used ngTemplateOutlet directive at all? With this in mind let’s dig deeper into some use cases.
Why use ngTemplateOutlet?
Although we know that we use angular ngTemplateOutlet directive for dynamic content loading and to save us lines of code. The question is how? and to illustrate this let’s consider the following use cases.
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-template-outlet',
templateUrl: './template-outlet.component.html',
styleUrls: ['./template-outlet.component.scss']
})
export class TemplateOutletComponent implements OnInit {
public items = [{
name: 'John',
age: 18,
dob: new Date(),
salary: 20000,
hobby: 'music',
occupation: 'engineer',
joiningDate: new Date(),
debt: 1000
},
{
name: 'Doe',
age: 28,
salary: 33000,
hobby: 'Reading',
joiningDate: new Date(),
debt: 2000
},
{
name: 'SomeOne',
salary: 29000,
hobby: 'music',
joiningDate: new Date(),
debt: 3000
}];
constructor() { }
ngOnInit() {
}
}
<h2>NgTemplateOutlet Example</h2>
<div *ngFor="let item of items">
<hr>
<div>
<label for="name">Name: </label>
<span *ngIf ="item.name" class="data-cell"> {{item.name}}</span>
<span *ngIf = "!item.name" class="no-data">N/A</span>
</div>
<div>
<label for="age">Age:</label>
<span *ngIf ="item.age" class="data-cell"> {{item.age}}</span>
<span *ngIf = "!item.age" class="no-data">N/A</span>
</div>
<div>
<label for="dob">Date Of Birth:</label>
<span *ngIf ="item.dob" class="data-cell"> {{item.dob | date}}</span>
<span *ngIf = "!item.dob" class="no-data">N/A</span>
</div>
<div>
<label for="occupation">Occupation:</label>
<span *ngIf ="item.occupation" class="data-cell"> {{item.occupation}}</span>
<span *ngIf = "!item.occupation" class="no-data">N/A</span>
</div>
<div>
<label for="salary">Salary:</label>
<span *ngIf ="item.salary" class="data-cell"> {{item.salary | currency}}</span>
<span *ngIf = "!item.salary" class="no-data">N/A</span>
</div>
<div>
<label for="hobby">Hobby:</label>
<span *ngIf ="item.hobby" class="data-cell"> {{item.hobby}}</span>
<span *ngIf = "!item.hobby" class="no-data">N/A</span>
</div>
<div>
<label for="debt">Debt:</label>
<span *ngIf ="item.debt" class="data-cell"> {{item.debt | currency}}</span>
<span *ngIf = "!item.debt" class="no-data">N/A</span>
</div>
<div>
<label for="joiningDate">Joining Date:</label>
<span *ngIf ="item.joiningDate" class="data-cell"> {{item.joiningDate | date}}</span>
<span *ngIf = "!item.joiningDate" class="no-data">N/A</span>
</div>
</div>
To begin with, in the above-mentioned case let’s see how we can optimize this code using the context object of ngTemplateOutlet. We can pass an object (context Object) to the *ngTemplateOutlet directive along with the template reference variable. Angular bind this object to the context of the ng-template. For this reason, we call this object context object. With this in mind let’s optimize our HTML code.
<h2>NgTemplateOutlet Example</h2>
<div *ngFor="let item of items">
<hr>
<ng-container *ngTemplateOutlet="useCase; context: {data: item.name, label: 'Name'}"></ng-container>
<ng-container *ngTemplateOutlet="useCase; context: {data: item.age, label: 'Age'}"></ng-container>
<ng-container *ngTemplateOutlet="useCase; context: {data: item.dob, label: 'Date Of Birth', isDate: true}">
</ng-container>
<ng-container *ngTemplateOutlet="useCase; context: {data: item.occupation, label: 'Occupation'}"></ng-container>
<ng-container *ngTemplateOutlet="useCase; context: {data: item.salary, label: 'Salary', isCurrency: true}">
</ng-container>
<ng-container *ngTemplateOutlet="useCase; context: {data: item.hobby, label: 'Hobby'}"></ng-container>
<ng-container *ngTemplateOutlet="useCase; context: {data: item.debt, label: 'Debt', isCurrency: true}">
</ng-container>
<ng-container *ngTemplateOutlet="useCase; context: {data: item.joiningDate, label: 'Joining Date', isDate: true}">
</ng-container>
</div>
<ng-template #useCase let-data="data" let-label="label" let-isDate="isDate" let-dollar="isCurrency">
<div>
<label [for]="label">{{label}}:</label>
<ng-container *ngIf="data; else noData">
<span *ngIf="isDate" class="data-cell">{{ data | date}}</span>
<span *ngIf="dollar" class="data-cell">{{ data | currency}}</span>
<span *ngIf="!isDate && !dollar" class="data-cell"> {{data}}</span>
</ng-container>
<ng-template #noData>
<span class="no-data">N/A</span>
</ng-template>
</div>
</ng-template>
We have passed the context object to the directive. This context object helped in reducing the lines of code and consequently a cleaner code. Even though in this example we did not save many lines but consider if there were more data fields. In that case, we have to write just a single line for <ng-container> by passing a context object for that data field.
Certainly, we are not limited to this approach of using the ngTemplateOutlet directive. context object can be created in .ts file as well. We can separate out the code in <ng-template #useCase> in a separate component altogether and use it throughout the project.