Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template binding with function return Observable and async pipe

Note this is simplified question of Angular template binding with Observable async pipe issue

template:

<div>{{foo()$ | async}}</div>

source code:

import { Component } from "@angular/core";
import { BehaviorSubject, of, Observable } from "rxjs";
import { tap, delay, map, switchMap, concatMap } from "rxjs/operators";

@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  private index = 0;
  foo$(): Observable<any> {
    console.log("aaa")
    return of("Delayed");
  }
}

The above code works as expected:

enter image description here

However if I added .pipe(delay(1)) to the foo$():

  foo$(): Observable<any> {
    return of("Delayed").pipe(delay(1));
  }

it won't work and keep "aaa" in the console log.

See https://stackblitz.com/edit/angular-qbhkg3

like image 640
Gelin Luo Avatar asked Sep 01 '26 11:09

Gelin Luo


1 Answers

A method called from the template, is called every change detection cycle. Because you are using the async pipe, the change detection is triggered with every emit. So basically you are creating an infinite loop and that's why it will never show a value, because the change detection never finishes:

  1. on view init template calls foo$()
  2. foo$() creates a -new- observable and delays the emit
  3. the observable emits after the delay
  4. the emit triggers a change detection from within the async pipe
  5. change detection calls foo$() from the template, and we're back to step 2

It's not really clear what you are trying to achieve, but I don't think you should return Observables from methods to be consumed in templates. These should be readonly class field:

readonly foo$ = of("Delayed").pipe(delay(1));

It is however possible to use a method, but you have to make sure this method returns the same Observable:

private readonly _foo$: Observable<any> = of("Delayed").pipe(delay(1));

foo$(): Observable<any> {
  console.log('here');
  return this._foo$;
}

example

Because the observable object stays the same (===), it's all good in the hood. Once you add a pipe to the Observable you create a new reference and you come back into the infinite loop.


The reason it does not reach the infinite loop if you just return of('delayed'), is because the Observable is not asynchronous this way. The Observable will return a value immediately to the async pipe, and when the async pipe calls detectChanges() nothing really happens, because it's still in the same cycle as the change detection cycle which triggered the foo$() template call.


I see you also linked a previous question you posted which involves the use of a decorator. You should change that decorator to a class field decorator, and you can then do the following:

@NeedsElement(sp(115621), ap(116215))
readonly insuredType$!: Observable<string>;

I think I can think of a way to make it work with a method call, but before I dive into that, I first want to know why you want it to be a method call in the first place

like image 159
Poul Kruijt Avatar answered Sep 03 '26 05:09

Poul Kruijt



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!