rodent_la
rodent_la

Reputation: 1395

Angular async pipe in template with long-lived observable return by method

I found below some interesting code.

According to the AsyncPipe description

The async pipe subscribes to an Observable or Promise and returns the latest value it has emitted. When a new value is emitted, the async pipe marks the component to be checked for changes. When the component gets destroyed, the async pipe unsubscribes automatically to avoid potential memory leaks. When the reference of the expression changes, the async pipe automatically unsubscribes from the old Observable or Promise and subscribes to the new one.

If the async pipe is used like below:

<div>{{ otherVariable }}</div>
<div>{{ getData() | async }}</div>
<span>{{ anotherVairable }}</span>
// The get data is observing the ngrx store data changes to reflect in the template
getData() {
    return this.ngRxStore.pipe(
        select(dataRx.getData),
        map(data => {
           // do some data conversion
           return value;
        }),
        takeUntil(this.destroyed$)
       );
}

I am just curious that when the template is rendered due to some other variables changes, will getData() be called again? Will Async pipe unsubscribe the old observable and subscribe to new observable of getData()?

I haven't use async pipe like this before, so I don't know is this a good usage like this?

Upvotes: 3

Views: 464

Answers (3)

Picci
Picci

Reputation: 17762

I think the answer to your question is the following.

If change detection is triggered, the getData() function is called.

But the getData() function just returns an Observable, i.e. it returns a set of functions (Observables are just functions), which does not mean that the functions are run.

So when change detection is run, you run getData() function which means that you create a new function to which the async pipe subscribes. When this occurs, the old Observable is unsubscribed, as per Angular documentation:

When the reference of the expression changes, the async pipe automatically unsubscribes from the old Observable or Promise and subscribes to the new one.

You can see this behavior in this stackblitz which is an elaboration of the one developed by @Naren Murali

In general I would suggest to stick to one style, either reactive or with 2-ways binding, and be consistent with it to avoid such mixed situations.

Upvotes: 1

Bastian Br&#228;u
Bastian Br&#228;u

Reputation: 791

Yes, getData() will be called every time change detection runs, and you will get a new Observable.

If you want to avoid this, you could store the observable in a local variable.

Upvotes: 0

Naren Murali
Naren Murali

Reputation: 56730

Take the below scenario, I have an input field where we can type anything, this should trigger change detection, but we will not see the async pipe triggered, but as mentioned in the documentation, if we emit an event from the subject by clicking the button, the async pipe run again (Note: its subscribed once and listens for new events)

you can do this by clicking on the button!

import { AsyncPipe } from '@angular/common';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { bootstrapApplication } from '@angular/platform-browser';
import { merge, Observable, of, Subject, Subscription, tap } from 'rxjs';
import 'zone.js';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [FormsModule, AsyncPipe],
  template: `
    <div class="card card-block">
  <h4 class="card-title">AsyncPipe</h4>

  <p class="card-text">data</p>
  <p class="card-text">{{ observable | async }}</p>
  <input type="text" [(ngModel)]="test" />
</div>
<button (click)="emitSubject()">Emit a new value</button>

  `,
})
export class App {
  observable: Observable<any>;
  subscription: Subscription = new Subscription();
  testSubject: Subject<void> = new Subject<void>();
  test = '';

  constructor() {
    this.observable = merge(this.testSubject, of([])).pipe(
      tap(() => {
        console.log('test');
      })
    );
  }

  emitSubject() {
    this.testSubject.next();
  }
}

bootstrapApplication(App);

stackblitz

Upvotes: 1

Related Questions