Dealing with async operations with the async pipe takes care of subscribing to Observable streams/async stuff like Promises for us.
There are a few common gotchas when dealing with purely cold Observables that somewhat pull in data (over perhaps, Http). There are also a few tricks we can use to mitigate common async issues, whilst being productive in templates.
Everytime we use the
async pipe, we create a subscription. If you’re going to subscribe directly to Observables that initiate data transfer, it’s likely you’ve come across unwanted issues such as duplicate Http requests.
There are, of course, ways around this using the
.share() operator in RxJS. But it’s more of a work-around, than a “work-with” me.
So let’s explore how we can handle
ngIf alongside the async pipe to alleviate some of these common issues.
ngIf and Async Pipe
Let me illustrate a common scenario inside a container/stateful component, where we’d typically use the async pipe to auto-subscribe and pass just raw data down:
<div> <user-profile [user]="(user$ | async)?.profile"> </user-profile> <user-messages [user]="(user$ | async)?.messages"> </user-messages> </div>
This approach has a few flaws, the first and most obvious is being potentially exposed to multiple, unwanted, subscriptions (previously mentioned up top) that initiate requests.
Secondly, we’re having to use the safe navigation operator
? before any property names. I don’t know about you, but I find this irritating - it doesn’t fill me with confidence that what I’m doing is structured correctly. Try avoid it wherever possible. We’ll refactor this component’s template before we finish with some best practices.
The Angular docs state that using the parentheses around the
user$ | async is classed an “inefficiency”. I personally find it extremely helpful at expressing intent when used appropriately.
So, what can we do to clean things up a little?
Using ngIf “as” syntax
This feature isn’t just for Observables (but I hope you’re using them anyway!). Let’s assume we’re using something like NGRX Store to make delivering state easier (which also mitigates things like multiple subscription issues that call upon new data requests).
Instead of waiting for each
user$ | async operation to be fully available, which likely requires more conditional checking further down inside the child presentational components, we can adopt a slightly different approach:
<div *ngIf="user$ | async as user"> <user-profile [user]="user.profile"> </user-profile> <user-messages [user]="user.messages"> </user-messages> </div>
Note the addition of “
as user” at the end of the expression.
What this will do is wait until
user$ | async has evaluated, and bind the result to the value of
prop$dollar suffix is generally used to indicate something is an Observable source.
user property has the resulting variable, you can use it anywhere inside that scope (inside the
ngIf, not outside).
This also gives us additional flexibility when displaying load state specific data to a user (loading/loaded):
<div *ngIf="user$ | async as user; else loading"> <user-profile [user]="user.profile"> </user-profile> <user-messages [user]="user.messages"> </user-messages> </div> <ng-template #loading> Loading stuff... </ng-template>
Read more on ngIf/else syntax.
My personal choice when adopting this syntax would be to use parentheses to express intent, visually it makes it far easier for me to see what’s going on without actually having to process the template in too much detail:
<div *ngIf="(user$ | async) as user; else loading"> <user-profile [user]="user.profile"> </user-profile> <user-messages [user]="user.messages"> </user-messages> </div> <ng-template #loading> Loading stuff... </ng-template>
A small deviation from the intention of this post, but a worthy mention. Ideally, data returned from either a selector or server response would be passed down as a whole - I find the syntax more reliable and extensible when passing props down to child components.
Something like this should suffice:
<div *ngIf="(user$ | async) as user; else loading"> <user-profile [user]="user"> </user-profile> <user-messages [user]="user"> </user-messages> </div> <ng-template #loading> Loading stuff... </ng-template>
All I’ve done here is remove
.messages from the
user binding. Pass the whole object down and use the pieces you need (in this case). Few reasons, namely type checking, interfaces, unit tests. Try it without and you’ll see your codebase explode into further complexity and lacking of structural types.
This approach of course, doesn’t just work with component bindings, you can use it anywhere. But ideally, async stuff should happen in container components, and presentational components should be simply given the data - to render.
Presentation components shouldn’t (in an ideal world) have to worry about checking if properties coming in through
@Input bindings actually exist before rendering. We can be smarter, and adopt better patterns through better tools.
And there’s one more for your toolbelt - the async pipe with
ngIf and the “as” syntax. It will store the result in a variable of your naming, and you can pass it wherever you like.