Note: As of writing (22-04-2020), ngrx/components is still experimental.
First, I'll go over the 'hack' that I'm talking about.
Let's suppose we have a rather uninteresting app that shows a different random value every second...
@Component({
selector: "app-root",
template: `
index: {{ (timer$ | async)?.index }}<br />
randomValue: {{ (timer$ | async)?.randomValue }}<br />
randomValue again:{{ (timer$ | async)?.randomValue }}
`
})
export class AppComponent {
timer$: Observable<{ index: number; randomValue: number }>;
constructor() {
this.timer$ = timer(0, 1000).pipe(
map((num: number) => {
return {
index: num,
randomValue: Math.random()
};
})
);
}
}
Look at that template. Ugh, we had to repeat the '(timer$ | async)' every time we want to render a property of the object. But it's actually worse than that. Each use of async creates a new subscription. See how the two randomValues are different? Perhaps not what we might have expected.
So we started using a little trick. *ngIf can store a conditional result in a variable. This is fully documented on the angular docs. It allows us to avoid repeating the async bit.
template: `
<div *ngIf="timer$ | async as randomNumbers">
index: {{ randomNumbers.index }}<br />
randomValue: {{ randomNumbers.randomValue }}<br />
randomValue again:{{ randomNumbers.randomValue }}
</div>
`
You'll now see that the two random numbers are the same. Woohoo, we only have one subscription. That's great and far more efficient. As a bonus, we didn't need to use the '?' safe navigation operator. Our div was only rendered when the observable was truthy.
So what's wrong with that? Sounds perfect. And it almost is. We don't like the fact that we co-opted the *ngIf for something that wasn't really a show/hide issue. In fact, we may have a different *ngIf statement that we want to use here. Also, using *ngIf here can interfere with angulars rendering and if your observable returns a falsy value, your div will hidden. Messy.
Along comes ngrxLet which comes with ngrx/components. This library as a set of reactive helpers. There is a desire in the angular community to move away from zones, and ngrx/components helps us do that.
template: `
<div *ngrxLet="timer$ as randomNumbers">
index: {{ randomNumbers.index }}<br />
randomValue: {{ randomNumbers.randomValue }}<br />
randomValue again:{{ randomNumbers.randomValue }}
</div>
`
It only took a small update to our code. *ngIf is still available to us if we want visibility logic. That's a better seperation of concerns.
There are a few other magical advantages to using ngrxLet. It also passes us '$error' and '$complete' from the underlaying observable. Like this...
template: `
<div *ngrxLet="timer$ as randomNumbers; let e = $error, let c = $complete">
index: {{ randomNumbers.index }}<br />
randomValue: {{ randomNumbers.randomValue }}<br />
randomValue again:{{ randomNumbers.randomValue }}
<div *ngIf="e">
error: {{e}}
</div>
<div *ngIf="c">
completed: {{c}}
</div>
</div>
`
I hope this makes it into a full release from ngrx. I like it.