In Angular, directives fall under 3 major categories:
ng-template falls under the category of structural directives, which help us define a template that doesn’t render anything by itself, but conditionally renders them to the DOM. It helps us create dynamic templates that can be customized and configured.
The easiest example to understand would be:
<!-- Easier sugar coated syntax --><div class="list" *ngIf="list.length else emptyList"><ul><li *ngFor="let item of list">{{item.name}}</li></ul></div><ng-template #emptyList><div>Sorry, The list is empty</div></ng-template><!-- OR Another more elaborate syntax--><ng-template [ngIf]="list.length" [ngIfElse]="emptyList"><ul><ng-template ngFor let-item [ngForOf]="list"><li>{{item.name}}</li></ng-template></ul></ng-template><ng-template #emptyList><div>Sorry, The list is empty</div></ng-template>
Here, the div will only be rendered if the list array is empty. The ng-template
helps us avoid ng-template
tag with a TemplateRef marked with # ( #emptyList in the above example). The first one is the concise syntax and the second one is the more elaborate.
No, if we try to do so, we will get the error “Uncaught Error: Template parse errors: Can’t have multiple template bindings on one element.”
So instead of doing this:
<p *ngFor="let item of list" *ngIf="list.length else emptyList">
<span> {{item.name}} </span>
</map>
We may need to introduce an additional wrapper component, however, this can be avoided by using ng-container
:
<ng-container *ngIf="list.length else emptyList">
<p *ngFor="let item of list">
<span> {{item.name}} </span>
</p>
</ng-container>
The advantage here is that we no longer need any additional wrapper elements.
We can instantiate the template anywhere on the page using ngTemplateOutlet directive:
<ng-container *ngIf="list.length else emptyList">
<ng-container *ngTemplateOutlet="itemList">
</ng-container>
</ng-container>
<ng-template #itemList>
<ul>
<ng-template ngFor let-item [ngForOf]="list">
<li>{{item.name}}</li>
</ng-template>
</ul>
</ng-template>
<ng-template #emptyList>
<div>Sorry, The list is empty</div>
</ng-template>
Here, we see that the itemList
template is supplied to the ng-container
via the *ngTemplateOutlet
directive.
Why does it matter?
Well, if we have many template snippets that we need to display based on conditions (e.g., an alert modal, a success model, a notification modal, etc.) we can have clear separation using the ng-template
and can apply conditions via the ng-container
.
The template is rendered only when the condition is met; so, layouts don’t get spoiled because there was an extra div that wrapped a container somewhere in the code.
Note: the scope is the same inside and outside of the template, i.e., the context is the same. However, if needed, each template has the luxury of providing its own set of input variables to the template.
<ng-container *ngIf="list.length else emptyList"><ng-container *ngTemplateOutlet="itemList; context:tag"> </ng-container></ng-container><ng-template #itemList let-myCategory="category" let-myFramework="framework"><h1>{{ myFramework }} / {{myCategory }}</h1><ul><ng-template ngFor let-item [ngForOf]="list"><li>{{item.name}}</li></ng-template></ul></ng-template><ng-template #emptyList><div>Sorry, The list is empty</div></ng-template>
Here, we pass the component property tag into the template’s context where two local properties myCategory and myFramework hold the value of the framework as well as the category. Note that the properties are local to the template and are not visible outside.
Imagine the possibilities there are if we create reusable shareable widgets! (e.g., custom tabs, custom checkout form with different steps, etc.)
But we are just scratching the tip of the iceberg here. With @ViewChild, it is possible to get a handle of the template inside the class of the component as a regular TemplateRef.