Angular - Observable with async pipe used multiple times in template... Good Practice or Bad?

10,457

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>
Share:
10,457
Mark
Author by

Mark

Updated on July 11, 2022

Comments

  • Mark
    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:

    1. Does the observable in the above code get called each time it is used via | async?
    2. 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?
    3. Which approach is better/preferred?

    Thanks!