Angular AsyncPipe in Depth
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 anObservable
orPromise
and returns the latest value it has emitted.
Consider the following example:
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.
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 })
Now it’s time to unsubscribe when the instance is destroyed. We can implement the OnDestroy
interface and perform the cleanup there.
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.
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:
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