簡體   English   中英

Angular:*ngIf 使用帶有異步管道的 concatMap 失敗

[英]Angular: *ngIf failing using concatMap with async pipe

如果我有一個Observable隨着時間的推移釋放值:

values$ = from([1, 2, 3, 'done'])
  .pipe(
    concatMap((x) => of(x).pipe(delay(1000)))
  );

我有一個函數返回對該Observable的訪問:

getOutputs(): Observable<'done' | number> {
    return this.values$;
  }

並使用*ngIfasync通過模板中的函數訂閱Observable

<div *ngIf="getOutputs() | async as val">
  <hello name="{{ val }}"></hello>
</div>

該行為是預期的:瀏覽器顯示“Hello 1!”、“Hello 2!”、“Hello 3!”、“Hello done!”,每間隔大約一秒。

相反,如果我將最新值存儲在BehaviorSubject中,並通過ngOnInit中的BehaviorSubject循環所有值:

outputs$ = new BehaviorSubject<number | 'done' | null>(null);
ngOnInit(): void {
    this.subscriptions.add(
      from<[number, number, number, 'done']>([
        1,
        2,
        3,
        'done',
      ]).subscribe((val) => this.outputs$.next(val))
    );
  }

行為當然是不同的:值都被發送到BehaviorSubjectoutputs$.value很快就“完成”了。 因此,以后出現的任何事情和訂閱都只會“完成”。 也是意料之中。

如果我將getOutputs()改為使用this.outputs$ ,我只會得到“Hello done!”:

getOutputs(): Observable<null | 'done' | number> {
    return this.outputs$;
  }

但是,如果我添加之前使用的相同concatMap ,如下所示:

getOutputs(): Observable<null | 'done' | number> {
    return this.outputs$
    .pipe(
      concatMap((x) => of(x).pipe(delay(1000)))
    );
  }

'done' 被一遍又一遍地發送,每秒一次(可以通過tap(console.log)看到),但模板什么也沒顯示。 這是出乎意料的:我認為 HTML 會顯示“Hello done!”。

為什么會這樣?

看到這個 Stackblitz

TL;博士

這是由 Angular 處理變更檢測的方式引起的。

Angular 會定期檢查您的視圖是否與模型中的數據保持同步,並且實際上每秒一次地不斷調用您的getOuputs()方法!


簡而言之,Angular 的變更檢測

考慮您的app.component.html模板:

<div *ngIf="getOutputs() | async as val">
  <hello name="{{ val }}"></hello>
</div>

在這里,Angular 會定期重新評估getOutputs() | async getOutputs() | async ,“幾次”,直到您的應用程序處於穩定狀態。

但是,對於每次評估,您都將返回一個新的、唯一的Observable ,因為您在getOutputs方法中創建了那個新的、唯一的Observable

public getOutputs(): Observable</* ... */> {
  return this.outputs$.pipe(
     concatMap(x => of(x).pipe(delay(1000))),
  ); // it's not `this.outputs$`, it's a **new Observable**
}

因此,如果您在哪里創建另一個這樣的成員:

 export class AppComponent implements OnInit, OnDestroy {
   private subscriptions = new Subscription();
   private outputs$ = new BehaviorSubject</* ... */>(null);
+  private actualOutputs$ = this.outputs$.pipe(
+    concatMap(x => of(x).pipe(delay(1000))),
+  );

   public getOutputs(): Observable</* ... */> {
+    return this.actualOutputs$;
-    return this.outputs$.pipe(
-       concatMap(x => of(x).pipe(delay(1000))),
-    );
   }
 }

(嗯,StackOverflow 不支持diff語法突出顯示,對此感到抱歉......)

...那么您的應用程序將完全按照您的預期運行!


更多探索

但是為什么消除延遲,但每次都產生不同的Observable也有效呢?

讓我們考慮以下getOutputs的替代實現:

public getOutputs(): Observable</* ... */> {
  return this.outputs$.pipe(
    concatMap(x => of(x)/* .pipe(delay(1000)) */), // no more delay here
  ); // that's a **new** Observable every time.
}

這是因為我(故意🙂)之前忽略了一些東西:
Angular 不會簡單地重新評估getOutputs()直到您的組件穩定,但實際上getOutputs() | async getOutputs() | async ,在純 TypeScript 世界中大致翻譯為async(getOutput())

這里發生的事情也有些簡單:
與前一種情況相反,當AsyncPipe不得不等待一個delayObservble時,它​​不會絕對穩定; AsyncPipe立即產生一個值,並且每次都產生相同的( 'done' ); Angular 認為您的組件“穩定”並繼續更新視圖。

如果您仔細調試代碼(或將console.log放在getOutputs()主體的頂部),您會注意到getOutputs()仍然快速連續調用 4 次,每次評估async(getOutputs())

為您的組件切換到更保守的changeDetection策略,如下所示:

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush, // different strategy here
})
export class AppComponent implements OnInit, OnDestroy {

... 將允許getOutputs()在這種情況下只執行一次

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM