Learn the Angular Pipe in-Depth + Tips on boosting performance using Pipe

yuvgeek

Yuvaraj

Posted on June 10, 2021

Learn the Angular Pipe in-Depth + Tips on boosting performance using Pipe

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:

  1. Use DatePipe to convert the Date object to a human-readable format.
  2. UpperCasePipe can be used to convert text to Uppercase.
  3. 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',
    },
  ];
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
  }
}

Enter fullscreen mode Exit fullscreen mode

Let's see it in detail on the created code.

  1. 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 the PipeTransform interface to perform the transformation.

  2. 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);
  }
}

Enter fullscreen mode Exit fullscreen mode

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'});
  }

}

Enter fullscreen mode Exit fullscreen mode

In the app.component.html, we are Iterating the items and

  1. Showing all the items in the first section
  2. Applied filterItems Pipe in the 2nd section and passed fruits as a second argument to it.
  3. Applied filterItems Pipe in the 3rd section and passed vegetables 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>
Enter fullscreen mode Exit fullscreen mode

This is the visual representation of how our Pipe is applied to the template and the type is passed as a second argument.

visual representation of our pipe

This is the output after applying our pipe.

Screenshot from 2021-06-09 18-36-49.png

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.

Screenshot from 2021-06-09 18-42-12.png

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);
  }
}

Enter fullscreen mode Exit fullscreen mode

Now, if you click the Add item, Lemon will be shown in the fruits section.

Screenshot from 2021-06-09 18-50-09.png

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

  1. 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.

  2. 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 with changeDetectorRef. For Impure Pipe, Angular executes the transform 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);
  }
}

Enter fullscreen mode Exit fullscreen mode

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.

Screenshot from 2021-06-09 19-10-30.png

Now, if the Add Item button is clicked, again transform function called 4 times, 2 for each instance.

Screenshot from 2021-06-09 19-14-32.png

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);
  }
}

Enter fullscreen mode Exit fullscreen mode

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'}]; 
  }
Enter fullscreen mode Exit fullscreen mode

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.

Screenshot from 2021-06-09 19-50-19.png

Now, click AddItem and see the console.

Screenshot from 2021-06-09 19-52-14.png

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!

You can connect with me on Twitter & Github.

💖 💪 🙅 🚩
yuvgeek
Yuvaraj

Posted on June 10, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related