Now you’ve learned the basics of Angular’s NgIf and Else it’s time to take things up a notch and introduce some Observables. In this article you’ll learn how to use Observables with Angular’s NgIf, using the async pipe and practices.
Our friend NgIf has a not-so-obvious feature that lets us will help us deal with asynchronous operations - via the async pipe takes care of subscribing to Observable streams 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.
Table of contents
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.
Free eBook
Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.
- Observables and Async Pipe
- Identity Checking and Performance
- Web Components <ng-template> syntax
- <ng-container> and Observable Composition
- Advanced Rendering Patterns
- Setters and Getters for Styles and Class Bindings
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 user
(non-dollar-suffixed).
The
prop$
dollar suffix is generally used to indicate something is an Observable source.
From this point, you can treat it like function scope in JavaScript. Once the 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 .profile
and .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.
Reference away!
To learn more techniques, best practices and real-world expert knowledge I’d highly recommend checking out my Angular courses - they will guide you through your journey to mastering Angular to the fullest!