25
votes

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!

3
This will be probably closed as opinion based but I am curious too :) - PeS
Unfortunate... I truly thought it was a good question. They are either the same, or one is way more efficient (I would have thought). - Mark

3 Answers

24
votes

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().

13
votes

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

4
votes

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.