How pure and impure pipes work in Angular Ivy

Post Editor

Understanding how pipes work under the hood by looking at their implementation details in Ivy

4 min read
1 comment
post

How pure and impure pipes work in Angular Ivy

Understanding how pipes work under the hood by looking at their implementation details in Ivy

post
post
4 min read
1 comment
1 comment

Angular’s piping mechanism is something Angular developers use everyday. There’s an excellent article that explores pipes in depth, and the gist of the article is the following:

When pipe is pure, transform() method is invoked only when its input arguments change. Pipes are pure by default.
If the pipe has internal state (that is, the result depends on the state other than its arguments), set pure to false. In this case, the pipe is invoked on each change detection cycle, even if the arguments have not changed.

Another interesting feature of the pure pipes is that Angular creates only one instance of a pure pipe regardless of how many times a pipe is used in a template:

<>Copy
<span [text]=”value | myCustomPurePipe”> <span [text]=”value | myCustomPurePipe”>

Here only one instance of myCustomPurePipe should be created.

This was true before Ivy, and in this article I’m going to take a look under the hood to find out if this still holds true in Ivy.

Setting things up
Link to this section

First thing first, let's create a new Angular project with version ≥ 9, because we are exploring pipes in Ivy implementation.

<>Copy
ng new study-pipes --style css --skip-tests true --routing false cd study-pipes

Create a custom pure and an impure pipe
Link to this section

Next we will create a custom pipe named my-custom-pure-pipe and my-custom-impure-pipe:

<>Copy
ng g pipe my-custom-pure-pipe --skip-tests true ng g pipe my-custom-impure-pipe --skip-tests true

Then, change the implementation of my-custom-pure-pipe like this:

<>Copy
@Pipe({ name: 'myCustomPurePipe', pure: true }) export class MyCustomPurePipe implements PipeTransform { constructor() { console.log('MyCustomPurePipe created'); } transform(value: number, ...args: any[]): any { console.log(`MyCustomPurePipe#transform called, value ${value}`); return value; } }

And change my-custom-impure-pipe like this:

<>Copy
@Pipe({ name: 'myCustomImpurePipe', pure: false }) export class MyCustomImpurePipe implements PipeTransform { constructor() { console.log('MyCustomImpurePipe created'); } transform(value: number, ...args: any[]): any { console.log(`MyCustomImpurePipe#transform called, value ${value}`); return value + value; } }

Basically here we're just logging at the instance creation stage and when the transform is called by Angular during change detection.

In app.component.ts, change the code as below:

<>Copy
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { number1 = 1; number2 = 2; }

And in angular.json file, change aot option at projects -> study-pipes -> architect -> build -> options -> aot from true to false to disable ahead of time (AOT) compilation. This will allow us to explore the generated code more easily.

Now we're finished with setting up the project. Let's start our exploration journey.

Exploring Pipes
Link to this section

Suppose in the app.component.html we have the following template

<>Copy
<span>{{ number1 | myCustomPurePipe }}</span> <span>{{ number2 | myCustomPurePipe }}</span> <span>{{ number1 | myCustomImpurePipe }}</span> <span>{{ number2 | myCustomImpurePipe }}</span>

Let's open the Chrome dev tools, in Source tab, and navigate to app component file, you will see this code:

There are two main if blocks in the generated code inside AppComponent_Template: the code executed during component instantiation rf & 1 and change detection logic rf & 2.

Here is the creation block:

<>Copy
if (rf & 1) { // this is the creation phase jit___elementStart_2(0,'span'); jit___text_3(1); jit___pipe_4(2,'myCustomPurePipe'); // pipe instance is created jit___elementEnd_5(); jit___elementStart_2(3,'span'); jit___text_3(4); jit___pipe_4(5,'myCustomPurePipe'); // pipe instance is created jit___elementEnd_5(); jit___elementStart_2(6,'span'); jit___text_3(7); jit___pipe_4(8,'myCustomImpurePipe'); // pipe instance is created jit___elementEnd_5(); jit___elementStart_2(9,'span'); jit___text_3(10); jit___pipe_4(11,'myCustomImpurePipe'); // pipe instance is created jit___elementEnd_5();

The jit__pipe_4 is actually the function to create a new instance of a pipe. So you can see that we'll have 4 instances of pipes. It means that in Ivy every pipe has its own instance, be it a pure or impure pipe. Whereas in View Engine, pure pipe has a shared instance.

Let's now look at the change detection block:

<>Copy
if (rf & 2) { jit___advance_6(1); jit___textInterpolate_7(jit___pipeBind1_8(2,4,ctx.number1)); jit___advance_6(3); jit___textInterpolate_7(jit___pipeBind1_8(5,6,ctx.number2)); jit___advance_6(3); jit___textInterpolate_7(jit___pipeBind1_8(8,8,ctx.number1)); jit___advance_6(3); jit___textInterpolate_7(jit___pipeBind1_8(11,10,ctx.number2)); }

The jit___pipeBind1_8 is the function to call transform on a pipe.

Here's the code when the pipe does the transform task.

<>Copy
// this code is called in update phase, or when change detection runs export function ɵɵpipeBind1(index: number, slotOffset: number, v1: any): any { const adjustedIndex = index + HEADER_OFFSET; // get LView, LView stands for Logical View const lView = getLView(); // get pipeInstance from LView const pipeInstance = load<PipeTransform>(lView, adjustedIndex); return unwrapValue( lView, // whether pipe is pure isPure(lView, adjustedIndex) ? // call pipe’s transform method or return from cache value pureFunction1Internal( lView, getBindingRoot(), slotOffset, pipeInstance.transform, v1, pipeInstance) : // pipe is impure then call pipe’s transform method directly pipeInstance.transform(v1)); }

From the above code, the isPure method will check whether a pipe is pure or impure by looking at the pure property in @Pipe decorator.

For impure pipes Angular calls the transform method on every change detection. For any input change to the pure pipe, it will call transform function. Otherwise it will return a cached value.

Conclusion
Link to this section

So, to conclude:

  • In Ivy, every pipe has its own instance, be it a pure or impure pipe. Whereas in View Engine, pure pipe has a shared instance. For example, in Ivy, if I use myCustomPurePipe in two places in a template, then, two instances of MyCustomPurePipe are created.
  • In a component which uses Default change detection strategy, when change detection happens, if the pipe is impure, then the transform method will be called. If the pipe is pure, whether there are any changes in input parameters in the transform method from the last call, then transform method will be called. Otherwise the pipe will return the cached value from last transform call.
  • When using impure pipe async, you should use it together with OnPush change detection to avoid unnecessary calls to transform on every change detection.

Here are two StackBlitz repos for you to play around, one is for View Engine, and another is for Ivy.

Thanks for your time and happy coding!

Comments (1)

authormorojenoe
2 June 2021

Hi Hien! In your opinion, why they changed that behavior in Ivy and started creating new instance for every pipe?

authorphhien203
7 June 2021

Hi Dmitry, It's a good question. I think the reason would be to leverage dependency injection. Here's a use case https://twitter.com/Waterplea/status/1380186091110686724

Share

About the author

author_image

Front-end developer at Inspectorio, Angular enthusiast

author_image

About the author

Hien Pham

Front-end developer at Inspectorio, Angular enthusiast

About the author

author_image

Front-end developer at Inspectorio, Angular enthusiast

Looking for a JS job?
Job logo
PDQ team| Senior JavaScript developer (Angular/Node)

SD Solutions

Ukraine
Remote
$60k - $80k
Job logo
Senior Full-Stack Developer (Node+Angular)

A-Listware

Ukraine
Remote
$48k - $78k
Job logo
Senior Full stack (Angular+Node)

Monolith

Ukraine
Remote
$60k - $84k
Job logo
AngularJS Developer/.net Core - Remote Contract

InfoMagnus

United States
Remote
$115k - $134k
More jobs
NxAngularCli
NxAngularCli
NxAngularCli

Featured articles

Angularpost
13 September 20218 min read
Tracking user interaction area

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Angularpost
13 September 20218 min read
Tracking user interaction area

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Read more
AngularpostTracking user interaction area

13 September 2021

8 min read

Explore one of the most complex pieces of Taiga UI — ActiveZone directive that keeps an eye on what region user is working with. It touches on low-level native DOM events API, advanced RxJS and Dependency Injection, ShadowDOM and more!

Read more
Angularpost
7 September 202122 min read
Designing Angular architecture - Container-Presentation pattern

Designing architecture could be tricky, especially in the agile world, where requirement changes are frequent. So your design has to support that and provides extendibility without the need for serious modification. In such cases, you will find the Container-Presentation pattern instrumental.

micro frontendspost
6 September 202125 min read
Taking micro-frontends to the next level

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

micro frontendspost
6 September 202125 min read
Taking micro-frontends to the next level

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

Read more
micro frontendspostTaking micro-frontends to the next level

6 September 2021

25 min read

The micro-frontends concept has been out there for quite a while. We’ve been using this architecture in Wix since around 2013, long before it was even given this name. In this article I’d like to share some of the things we did in order to evolve the concept of developing big scale micro-frontends.

Read more