Using Embedded Angular Templates

July 27th 2018 Angular

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 my ng-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 named person and assigned it the value of teacher (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.

Copyright
Creative Commons License