Angular - Observable with async pipe used multiple times in template... Good Practice or Bad?
Solution 1
Using the async pipe makes handling subscriptions much easier. It automatically handles unsubscribing unlike subscribing in the component.
That said, there is a better pattern than what the example is showing. Rather than having multiple async calls on components, you can write it 2 different ways. I'm assuming these components are in the same template file:
<div *ngIf="(myObservable$ | async) as myObservable">
<my-random-component [id]="myObservable.id">
<my-random-component2 [name]="myObservable.name">
</div>
Wrapping the code in ngIf
does 2 things:
- It cuts down on duplicate code
- The components do not exist until
myObservable$
is ready
There's also one more idea if you want to stick with calling async every single time:
// COMPONENT
name$: Observable<string>;
id$: Observable<string>;
ngOnInit() {
// Return the exact value you want rather than the full object
this.name$ = OBSERVABLE_SOURCE
.pipe(
map(res => res.name)
);
this.id$ = OBSERVABLE_SOURCE
.pipe(
map(res => res.id)
);
}
// TEMPLATE
<my-random-component [id]="(id$ | async)">
<my-random-component2 [name]="(name$ | async)">
Pipes do not automatically run without a subscription. You can map, tap, or do anything else you want with it and it will not run until you add async/.subscribe()
.
Solution 2
If you have multiple observables, you could wrap your entire page in a div that collects all the observables into a data object and then use them as needed :
<div *ngIf="{
observable1: myObservable1$ | async,
observable2: myObservable2$ | async
} as data">
... page content
{{data.observable1.id}}: {{data.observable1.name}}
{{data.observable2.status}}
</div>
Note: the *ngIf="{ ... }"
is always true.
Credit goes to: https://medium.com/@ofirrifo/extract-multiple-observables-with-the-async-pipe-in-angular-b119d22f8e05
Solution 3
You can just use share()
to use the same observable and call it multiple times from html. Like this:
this.myObservable$ = this.anotherObservable$.pipe(share());
Then no matter how many times you call the observable from the HTML, it is called only once.
Solution 4
Another approach would be separating both structure and content rendering responsibilities without being bound to *ngIf
.
You could use ng-template
and ng-container
together with context:
<ng-template #userTemplate let-user>
<user-address [zipCode]="user?.zipCode"></user-address>
<user-car [carCode]="user?.carCode"></user-car>
</ng-template>
<ng-container
*ngTemplateOutlet="
userTemplate;
context: { $implicit: user$ | async }
"
>
</ng-container>
Mark
Updated on July 11, 2022Comments
-
Mark almost 2 years
If I have the need to bind multiple properties from the same observable within my component template...
For example:
<my-random-component[id]="(myObservable$ | async).id"> ... <my-random-component2[name]="(myObservable$ | async).name">
...am I better off doing it like I have above (which I see a lot), or is it more efficient to subscribe to my observable inside my .ts file, set a single object variable, and then bind to that? The idea with the latter approach being that the observable will only be called once.
Questions:
- Does the observable in the above code get called each time it is used via | async?
- Does the compiler do any efficiency magic behind the scenes to only call the observable once even if used 10 times w/in my template?
- Which approach is better/preferred?
Thanks!