Bogac
Bogac

Reputation: 3707

Performance of using same observable in multiple places in template with async pipe

In my component template I am calling async pipe for same Observable in 2 places.

Shall I subscribe to it and use returned array in my template or using async pipe for same Observable in multiple places of template has no negative effect to performence?

Upvotes: 41

Views: 12732

Answers (6)

NsdHSO
NsdHSO

Reputation: 153

You can make it like this, and in the template use the local variable

products$ = this._httpClient.get<Product[]>(`${this._environment.apiProducts}/product`)
.pipe(tap(data => console.table(data)),
  share()
);

// Template

  <ng-container *ngIf="products$ | async as products; else loading">
<div *ngFor="let product of products"></...>
<div *ngIf="products..."></...>
</ng-container>

I hope this post solves your problem

Upvotes: 0

pegaltier
pegaltier

Reputation: 559

Solution provided above by @Hinrich is very good however sometimes you are blocked because you want to use multiple observables in the template, in this case there is a simple solution like that (which work well for hot observables like a NgRx selector but maybe not well for a cold observable like http requests) :

@Component({
    selector: "some-comp",
    template: `
      <ng-container *ngIf="{ book: squareData$ | async, user: otherData$ | async } as data">
        Sub1: {{data.squareData}}<br>
        Sub2: {{data.otherData}}<br>
      </ng-container>
    `
})

Upvotes: 2

jdforsythe
jdforsythe

Reputation: 1067

We use the @Hinrich solution but instead of the @pegaltier solution for multiple Observables, we use combineLatest().

this.data$ = combineLatest(book$, user$)
  .pipe(
    map(([book, user]) => {
      return (book && user) ? { book, user } : undefined;
    }),
  );
<ng-container *ngIf="data$ | async as data">
  {{ data.book }} {{ data.user }}
</ng-container>

Upvotes: 0

Roy Art
Roy Art

Reputation: 703

Another way of avoiding multiple subscriptions is to use a wrapping *ngIf="obs$ | async as someName". Using olsn's example

    @Component({
        selector: "some-comp",
        template: `
          <ng-container *ngIf="squareData$ | async as squareData">
            Sub1: {{squareData}}<br>
            Sub2: {{squareData}}<br>
            Sub3: {{squareData}}
          </ng-container>`
    })
    export class SomeComponent {
        squareData$: Observable<string> = Observable.range(0, 10)
            .map(x => x * x)
            .do(x => console.log(`CalculationResult: ${x}`)
            .toArray()
            .map(squares => squares.join(", "));

    }

It's also cool because it cleans out the template a bit too.

Upvotes: 18

squirrelsareduck
squirrelsareduck

Reputation: 884

I had better luck with .shareReplay from 'rxjs/add/operator/shareReplay' which is very new (https://github.com/ReactiveX/rxjs/pull/2443)

I also had luck with .publishReplay.refCount(1) (Angular 2 + rxjs: async pipe with .share() operator)

I'm honestly not sure about the difference between the two strategies. The comments in the PR for shareReplay suggest that there might be more risk for memory leaks of Subscriptions if not implemented correctly, so I might go with the .publishReplay.refCount(1) for now.

Upvotes: 2

Olaf Horstmann
Olaf Horstmann

Reputation: 16892

Every use of observable$ | async will create a new subscription(and therefor an individual stream) to the given observable$ - if this observable contains parts with heavy calculations or rest-calls, those calculations and rest-calls are executed individually for each async - so yes - this can have performance implications.

However this is easily fixed by extending your observable$ with .share(), to have a shared stream among all subscribers and execute all those things just once for all subscribers. Don't forget to add the share-operator with import "rxjs/add/operator/share";

The reason why async-pipes don't share subscriptions by default is simply flexibility and ease of use: A simple .share() is much faster to write than creating a completely new stream, which would be required if they were to be shared by default.

Here is a quick example

@Component({
    selector: "some-comp",
    template: `
        Sub1: {{squareData$ | async}}<br>
        Sub2: {{squareData$ | async}}<br>
        Sub3: {{squareData$ | async}}
    `
})
export class SomeComponent {
    squareData$: Observable<string> = Observable.range(0, 10)
        .map(x => x * x)
        .do(x => console.log(`CalculationResult: ${x}`)
        .toArray()
        .map(squares => squares.join(", "))
        .share();  // remove this line and the console will log every result 3 times instead of 1
}

Upvotes: 135

Related Questions