Hello everyone ๐,
In this article, we are going to cover what is Angular Pipe, how to create it and utilize it in our template. Additionally, we will learn how to boost performance with a custom Pipe.
What is Pipe in Angular?
From Angular Documentation,
Use pipes to transform strings, currency amounts, dates, and other data for display. Pipes are simple functions you can use in template expressions to accept an input value and return a transformed value.
Usecase of pipe:
- Use DatePipe to convert the Date object to a human-readable format.
- UpperCasePipe can be used to convert text to Uppercase.
- CurrencyPipe helps to transform a number to a currency string, formatted according to locale rules.
The best advantage to use Pipe is, while transforming the data, it doesn't modify the original data. Let's see it in action.
Creating a Custom Pipe
You can create a custom Pipe only when it is not available in the built-in Pipe.
We are going to create a Pipe which filters the items as fruits/vegetables based on type
property.
const items = [
{
name: 'Tomato',
type: 'vegetables',
},
{
name: 'Orange',
type: 'fruits',
},
{
name: 'Apple',
type: 'fruits',
},
];
Our objective is to show all the items in the first section, then show only fruits in the second section & vegetables in the third section.
First, let's create a Pipe with the below ng
command.
ng generate pipe filter-items
The command creates a file as filter-items.pipe.ts
with the following code.
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'filterItems'
})
export class FilterItemsPipe implements PipeTransform {
transform(value: unknown, ...args: unknown[]): unknown {
return null;
}
}
Let's see it in detail on the created code.
ng command created a class and applied
@Pipe
decorator with name as a property. This is the name of the created pipe. Then it implements thePipeTransform
interface to perform the transformation.Angular invokes the
transform
method with the value of a binding as the first argument, and any parameters as the second argument in list form, and returns the transformed value.
Imagine, the transform
is just a function, to which the original item is passed as a first argument and any parameters as the second argument in list form.
Now, update the transform
function to filter the items based on the type
property.
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'filterItems'
})
export class FilterItemsPipe implements PipeTransform {
transform(value: any[], type: string): any[] {
return value.filter(el => el.type === type);
}
}
Applying the Custom Pipe to template
This is our app.component.ts
which has items
and a method addItem
.
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styles: []
})
export class AppComponent {
items = [
{
name: 'Tomato',
type: 'vegetables',
},
{
name: 'Orange',
type: 'fruits',
},
{
name: 'Apple',
type: 'fruits',
},
];
addItem() {
this.items.push({name: 'Lemon', type: 'fruits'});
}
}
In the app.component.html
, we are Iterating the items and
- Showing all the items in the first section
- Applied
filterItems
Pipe in the 2nd section and passedfruits
as a second argument to it. - Applied
filterItems
Pipe in the 3rd section and passedvegetables
as a second argument to it.
When we apply a pipe in the template, automatically the value on which pipe is applied is passed as a first argument to transform
and an additional argument can be passed by adding :(colon)
and then value.
<div>
<h1>Original data</h1>
<div *ngFor="let item of items">
<p>{{item.name}} - {{item.type}}</p>
</div>
<h1>Filtered fruits</h1>
<div *ngFor="let item of items | filterItems: 'fruits'">
<p>{{item.name}} - {{item.type}}</p>
</div>
<h1>Filtered Vegetables</h1>
<div *ngFor="let item of items | filterItems: 'vegetables'">
<p>{{item.name}} - {{item.type}}</p>
</div>
<button type="button" (click)="addItem()">Add Item</button>
</div>
This is the visual representation of how our Pipe
is applied to the template and the type is passed as a second argument.
This is the output after applying our pipe.
Yay! ๐ this is what we wanted. You can see that filtering the data happened by without modifying the original items.
Let's try clicking the Add Item
button and see if lemon
is shown in the fruits section.
Lemon
is shown in the original data section, but it doesn't show in the fruits
section.
Why? ๐ค
The reason is, when a Pipe is created, it will be set as a Pure Pipe by default. Also, in the addItem
method, the lemon
is pushed to the same array. So, Angular doesn't know that there is a change in the value. Click here to learn more about it from Angular
documentation.
To fix it, the Pipe can be changed to Impure Pipe which runs the transform
function on all Angular Change Detection (or) create a new array every time when a new item is added to it.
First, we will see the first approach on changing to Impure Pipe.
Open the created pipe, and add pure
to false in the @Pipe
decorator.
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'filterItems',
pure: false
})
export class FilterItemsPipe implements PipeTransform {
transform(value: any[], type: string): any[] {
return value.filter(el => el.type === type);
}
}
Now, if you click the Add item
, Lemon will be shown in the fruits
section.
Setting Pure
to false (Impure Pipe) solves the issue but let's discuss why it doesn't work with Pure Pipe.
Pure vs Impure Pipe
Before Ivy, Pure Pipe creates only one instance of a class whereas Impure pipe creates many instances if it used in multiple places. In our example, we have used
filterItems
pipe for the 2nd and the 3rd section. So, it will create 2 instances of the class.For Pure Pipe, the
transform
function in the Pipe will be called only when there is a change in the@Input()
, change in the value passed to the pipe (for Object & Array it should be new reference) or forcefully running the change Detection withchangeDetectorRef
. For Impure Pipe, Angular executes thetransform
every time it detects a change with every keystroke or mouse movement.
If you are not using the Ivy engine, then if your page has 30 components uses Impure Pipe, and whenever there is a change in the mouse move, 120 times the transform
function will be triggered with 30 instances of Pipe. ๐คฏ
If you are using Ivy Engine, then be it Pure or Impure pipe, multiple instances will be created.
But the condition on triggering the transform
function and the no of times is called are depends on the Pure or Impure Pipe.
As the latest Angular version has Ivy set as true by default, we will see the examples considering the view engine as Ivy.
In our pipe, the id
property is created and a unique id is assigned to the instance of the class through the constructor
.
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'filterItems',
pure: false
})
export class FilterItemsPipe implements PipeTransform {
// unique id for the instance
id;
// assigns the unique id for the instance
constructor() {
this.id = Math.floor(Math.random() * 10);
console.log('unique id => ', this.id);
}
transform(value: any[], type: string): any[] {
return value.filter(el => el.type === type);
}
}
Refresh the application and open the console tab in the DevTools.
As we've used pipe 2 times, one for fruits
and the other for vegetables
, 2 instances of the pipe is created with unique id as 6
& 3
. And the transform
function is called 8 times, 4 for each instance.
Now, if the Add Item
button is clicked, again transform
function called 4 times, 2 for each instance.
Additionally, Angular runs this transform
function every time it detects a change with every keystroke or mouse movement.
Just Imagine, a bigger application that has 100+ components in UI with many impure pipe. ๐คฏ
To fix this performance issue, Pure Pipe should be used with some modification in the application code.
Boost Performance with Pure Pipe
Let's fix this performance issue by following the below steps.
Change the Pipe to Pure
by setting pure: true
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'filterItems',
pure: true
})
export class FilterItemsPipe implements PipeTransform {
// unique id for the instance
id;
// assigns the unique id for the instance
constructor() {
this.id = Math.floor(Math.random() * 10);
console.log('unique id => ', this.id);
}
transform(value: any[], type: string): any[] {
return value.filter(el => el.type === type);
}
}
Then, open app.component.ts
and update the code in addItem
method.
addItem() {
// push an element doesn't change reference.
// this.items.push({name: 'Lemon', type: 'fruits'});
// Angular Change Detection runs when it sees a change in the Array as new reference
this.items = [...this.items, {name: 'Lemon', type: 'fruits'}];
}
Run the application and see the console tab.
2 instance of the pipe is created (because of Ivy), and the transform
function is triggered 2 times, one for each.
Now, click AddItem
and see the console.
The transform
function is triggered 2 times, one for each.
Conclusion
Comparing with Pure with Impure Pipe, using Impure Pipe triggered 8 times the transform
function first, and on clicking AddItem
, 4 times it triggered & also whenever this is a mouse over or user interaction happens it will call multiple times again and again. But using Pure pipe, it triggers 4 times totally.
So, always use the Pure Pipe.
Thanks for reading the article, I hope you liked it!