Defining generic views with TemplateRef

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:

Defining generic views with TemplateRef

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset