Using Embedded Angular Templates
When you want to reuse a part of markup in Angular, you usually wrap it into a separate component which is not limited to markup only. It can include its own code as well.
However, sometimes a component can be an overkill. You might have only a couple of lines of markup which you need to repeat in multiple places inside a single component but nowhere else. Of course, you could just copy and paste that markup and be done with it:
<div>
<p>Teachers:</p>
<ul>
<li *ngFor="let teacher of teachers">
{{teacher.name}} {{teacher.surname}} ({{teacher.age}})
</li>
</ul>
<p>Students:</p>
<ul>
<li *ngFor="let student of students">
{{student.name}} {{student.surname}} ({{student.age}})
</li>
</ul>
</div>
In the contrived example above this might very well even be the best approach. Still, it's often a good idea to adhere to the DRY (don't repeat yourself) principle. If the way the persons are rendered will change in the future, there's always danger that you will forget to change both occurrences and that they will diverge with time. The risk becomes even greater if there are more than two occurrences and not all of them are fully executed every time a page is rendered because of conditions that apply to them.
You might still have a hard time justifying the creation of another component for that reason alone. In that case, embedded templates might be just the right tool for the job. The markup that needs to be repeated can be moved inside an ng-template
element:
<ng-template #person let-person="person">
<li>{{person.name}} {{person.surname}} ({{person.age}})</li>
</ng-template>
The #person
syntax is used to define a name for the template so that it can be referenced from elsewhere. The attributes with the let-
prefix define input variables which can be used inside the template. In the above sample, a variable named person
is declared and assigned the person
field of the context object as its value. That's why person
can be referenced in the interpolation expressions.
The context object is defined when the template is referenced from an ngTemplateOutlet
directive:
<ng-container *ngFor="let teacher of teachers">
<ng-container *ngTemplateOutlet="person;context:{person: teacher}"></ng-container>
</ng-container>
The renderer will replace the inner ng-container
element with the contents of the ng-template
element I defined before. All the required information is passed into the ngTemplateOutlet
directive:
- The part before the semicolon is the name of the template to be used,
person
in my case because of#person
in myng-template
element. - The part following the
context:
syntax is the definition of the context object that will be passed to the template. I created an object with a single field namedperson
and assigned it the value ofteacher
(a variable name declared in the line before).
As already mentioned before, the let-person
attribute in my ng-template
element will assign the value of the person
field I created here to the person
input variable which will be valid inside the template.
With all that explained, I can now create the full markup equivalent to the one at the beginning of this post:
<div>
<p>Teachers:</p>
<ul>
<ng-container *ngFor="let teacher of teachers">
<ng-container *ngTemplateOutlet="person;context:{person: teacher}"></ng-container>
</ng-container>
</ul>
<p>Students:</p>
<ul>
<ng-container *ngFor="let student of students">
<ng-container *ngTemplateOutlet="person;context:{person: student}"></ng-container>
</ng-container>
</ul>
</div>
<ng-template #person let-person="person">
<li>{{person.name}} {{person.surname}} ({{person.age}})</li>
</ng-template>
Although it is longer than the original one, the markup for rendering a person (a teacher or a student) is not repeated any more. It is defined only once inside the ng-template
element at the bottom and referenced using the ngTemplateOutlet
directive for both the teacher and the student. The new markup adheres to the DRY principle and if I had to change how a person is rendered, I only need to do it in a single place - inside the ng-template
element.