Custom Pipes
Click here to run Live example for the demo of this section.
Lets jump straight into how we can create and consume a pipe. Lets start off by creating a simple filter pipe. For demonstration purpose we have a simple to-do app that has two components. The first one is the TodoListComponent
that has an Array of to-dos. The second one TodoItemComponent
is the child component that renders an individual to-do item that we pass it from the parent.
Here is a simple filter pipe
import { Pipe, PipeTransform } from '@angular/core';
import { Todo } from './todo';
@Pipe({name: 'todoFilter'})
export class TodoFilterPipe implements PipeTransform {
transform(todos, filter) {
if(filter)
return todos.filter(todo => todo.content.startsWith(filter));
return todos;
}
}
The TodoFilterPipe
is decorated with the Pipe
metadata that defines the name that we use in the template. Also note that our pipe class implements the PipeTransform
interface that has the transform(...)
method. This is the method that Angular will call with paramters when ever our pipe is used.
Consuming Pipes
In our case the TodoFilterPipe
takes in a list of to-dos and filters it based on the filter
parameter. Here is how our pipe is used inside the TodoListComponent
template:
import { Component } from '@angular/core';
import { Todo } from './todo';
import { TodoItem } from './todo-item.component';
import { TodoFilterPipe } from './todo-filter.pipe';
@Component({
selector: 'todo-list',
directives: [TodoItem],
pipes: [TodoFilterPipe],
template: `
<input type="text" placeHolder="filter todos" [(ngModel)]= "filterText">
<p>List of Todos: </p>
<ul>
<todo-item *ngFor="let todo of todos | todoFilter: filterText" [todo]="todo"></todo-item>
</ul>
<small>Total items: {{todos.length}}</small>
`
})
export class TodoList {
public filterText:string;
public todos: Array<Todo>;
}
Of particular interest is how we have used the TodoFilterPipe
with *ngFor
and how the filterText
is passed as a parameter using the :
symbol.
<todo-item *ngFor="let todo of todos | todoFilter: filterText" [todo]="todo"></todo-item>
Angular invokes the transform
method of the pipe with the value of the binding as the first argument. Notice how the parameter name is not included in the expression like we used in the built-in date pipe.
If you run the live example you will see that the pipe now filters our todos based on the filter text that we type.
Purity of Pipes
Let create another pipe that hides completed to-do item based on user input. This is where we will run into some interesting stuff. Here is the TodoDonePipe
for that:
import { Pipe, PipeTransform } from '@angular/core';
import { Todo } from './todo';
@Pipe({name: 'todoDone'})
export class TodoDonePipe implements PipeTransform {
transform(todos: Array<Todo>, hideDone: boolean) {
if(hideDone)
return todos.filter(todo => todo.done == false);
else
return todos;
}
}
This pipe filters out the To-do's that are marked done if the hideDone
is true. Lets chain it with our filter pipe:
<todo-item *ngFor="let todo of todos | todoFilter: filterText | todoDone: hideCompleted" [todo]="todo"></todo-item>
Now you can toggle completed tasks with a check box that binded to hideCompleted
. If you run the live Example and add a new todo item with the Hide Completed checked, you will see that it does not show up in the list unless you check it again. Whats happening here? This leads us to explore pure and impure pipes.
Pure pipes
By default pipes are pure. Angular optimizes the pure pipes by checking if they need to be invoked again if the inputs change. In the above example the items are pushed to the to-do array i.e. todos.push()
. This does not change the reference to the array. Instead we are mutating the array object by adding more items to it.
Because in our case the reference does not change, angular does not invoke our pipe. Hence the view goes out of sync with our model. This is the way Angular change Detection works.
You might be wondering why this optimization? Well, change detection can be an expensive operation and it can even crash your app if not done right. Change detection is in itself a whole topic that we will cover in later chapters.
Impure pipes
Impure pipes are executed whenever Angular fires the change detection cycle regardless of change in its input value. Because it is executed every time, we need to make sure our pipes don't do expensive operations which can ruin user experience. Creating impure pipes is simply a case of turning off the pure
flag in pipe metadeta.
import { Pipe, PipeTransform } from '@angular/core';
import { Todo } from './todo';
@Pipe({
name: 'todoDone'
pure: false
})
export class TodoDonePipe implements PipeTransform {
transform(todos: Array<Todo>, hideDone: boolean) {
if(hideDone)
return todos.filter(todo => todo.done == false);
else
return todos;
}
}