We are already familiar with the concepts of inputs, content children, and view children, and we also know when we can get a reference to them in the component's life cycle. Now, we will combine them and introduce a new concept-TemplateRef
.
Let's take a step back and take a look at the last to-do application we developed earlier in this chapter. In the following screenshot, you can see what its UI looks like:
Figure 11
If we take a look at its implementation in ch4/ts/inputs-outputs/app.ts
, we'll see that the template used to render the individual to-do items is defined inside the template of the entire to-do application.
What if we want to use a different layout to render the to-do items? We can do this by creating another component called Todo
, which encapsulates the responsibility of rendering them. Then, we can define separate Todo
components for the different layouts we want to support. This way, we need to have n different components for n different layouts, even though we need to change only their templates.
Angular comes with a more elegant solution. Earlier in this chapter, we have already discussed the template element. We said that it allows us to define a chunk of HTML that will not be processed by the browser. Angular allows us to reference such template elements and use them by passing them as content children.
Here is how we can pass the custom layout to our refactored TodoApp
component:
// ch4/ts/template-ref/app.ts <todo-app> <template let-todo> <input type="checkbox" [checked]="todo.completed" (change)="todo.completed = !todo.completed;"> <span [class.completed]="todo.completed"> {{todo.label}} </span><br> </template> </todo-app>
In the template, we declare a variable called todo
. Later in the template, we can use it to specify the way in which we want to visualize the content.
Now, let's take a look at how we can get a reference to this template in the controller of the TodoApp
component:
// ch4/ts/template-ref/app.ts class TodoApp { @ContentChild(TemplateRef) itemsTemplate: TemplateRef; // ... }
All we do here is define a property called itemsTemplate
and decorate it with the @ContentChild
decorator. During the component's life cycle (more accurately, in ngAfterContentInit
), the value of itemsTemplate
will be set to a reference of the template that we passed as the content of the todo-app
element.
There is one more problem though – we need the template in the TodoList
component, since that's the place where we render the individual to-do items. What we can do is define another input of the TodoList
component and pass the template directly from TodoApp
:
// ch4/ts/template-ref/app.ts
class TodoList {
@Input() todos: Todo[];
@Input() itemsTemplate: TemplateRef;
@Output() toggle = new EventEmitter<Todo>();
}
We need to pass it as an input from the template of TodoApp
:
... <todo-list [todos]="todos" [itemsTemplate]="itemsTemplate"> </todo-list>
The only thing left is to use this template reference in the template of the TodoList
application:
<!-- ... --> <template *ngFor="let todo of todos; template: itemsTemplate"></template>
We have explained the extended syntax of the ngForOf
directive in the previous sections of this chapter. The preceding snippet shows one more property of this directive that we can set: the ngForTemplate
property. By default, the template of the ngForOf
directive is the element it is used on. By specifying a template reference to the ngForTemplate
property, we can use the passed TemplateRef
instead.