Angular AsyncPipe in Depth

Dmitry Drobyshev
IT’s Tinkoff
Published in
3 min readAug 16, 2021

Hi there! My name is Dima, and I work as a front-end developer in Tinkoff.

We often use the AsyncPipe to display async data in templates. Recently, I decided to find out how it works in detail.

What is AsyncPipe?

The AsyncPipe subscribes to an Observable or Promise and returns the latest value it has emitted.

Consider the following example:

StackBlitz

interval(1000) creates a new Observable, which returns a new value every 1 second. We’ve subscribed to the template with the AsyncPipe and shown the last emitted value. This approach is more straightforward than subscribing inside the OnInit hook.

Wait. What about unsubscribing? Don’t worry! The AsyncPipe handles this. It unsubscribes from Observable when the component is destroyed. Thus, the AsyncPipe is an excellent tool for dealing with asynchronous data.

We can use data$ | async as data with structural directives such as *ngIf and *ngFor to avoid subscribing to the same data twice.

StackBlitz

We’ve subscribed to user$ with async in the template and defined a new variable, which we can reuse inside the <div> tag.

Also, we get a property of the emitted object if we wrap obs$ | async in parentheses.

*ngIf="(user$ | async).name as name"

Unfortunately, we will get the Cannot read property ‘name’ of null error at runtime. I’ll explain it later. Use Optional Chaining and add a question mark before obtaining the property to prevent this problem.

*ngIf="(user$ | async)?.name as name"

We’ve covered what the AsyncPipe is and how to use it. Now I suggest finding out how it works under the hood. Let’s write our implementation of the AsyncPipe based on the original pipe step by step.

Own AsyncPipe?

We’re going to start with the contract. For the sake of simplicity, we aren’t paying attention to working with Promise. As a result, our pipe must get an Observable and return the emitted values. It’s easy as pie! Let’s make the first draft:

Our pipe subscribes to an Observable and returns the undefined in the first transform() call. When the Observable returns a value, the next transform() call will return the last emitted value. The “Initial Null Problem” occurs because the first call in the original pipe returns null.

However, transform() is only called when the input data changes by default. This is a fantastic feature of Angular pipes, which reduces the calculation (read more). Since the Observable comes only once, we need to set pure:false inside the metadata of the pipe, then Angular will know our desire to call transform() on every change detection cycle.

@Pipe({ name: 'myAsync', pure: false })

StackBlitz

Now it’s time to unsubscribe when the instance is destroyed. We can implement the OnDestroy interface and perform the cleanup there.

StackBlitz

myAsync already has the right to exist. But, if a component changeDetection property is set to ChangeDetectionStrategy.OnPush, our pipe’s data will not be updated. The change detection cycle will not pass through the component, since the input properties of the component aren’t changed. Thus, the transform() function will be invoked once when the component is initialized.

We need to inject ChangeDetectorRef and execute markForCheck() on each Observable emit to fix this.

StackBlitz

After that, our pipe fits the definition from the documentation. I’d like to bring up another point. We can only work with one Observable in the current version of myAsync. If we provide more than one, then the next Observable will be ignored. Better to unsubscribe from the first Observable and subscribe to the last one. Let’s do this:

StackBlitz

The dispose() method now performs the cleanup. It is invoked on the OnDestroy hook. If we have a new Observable, then unsubscribe from the first and execute transform() to subscribe to the new one. That is a tricky concept; better try StackBlitz 🙈

But now our code is almost identical to the original AsyncPipe.

Summary

We have finally finished our research. We’ve gone over what the AsyncPipe is, how to use it, and learned everything about its guts. I hope you found this article helpful. Thank you for reading.

And keep in mind — with great counts of subscriptions comes great responsibility

--

--