Template binding with function return Observable and async pipe

蓝咒 提交于 2020-05-24 03:38:49

问题


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:

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


回答1:


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



来源:https://stackoverflow.com/questions/61772610/template-binding-with-function-return-observable-and-async-pipe

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!