[英]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$;
}
並使用*ngIf
和async
通過模板中的函數訂閱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))
);
}
行為當然是不同的:值都被發送到BehaviorSubject
, outputs$.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 。
這是由 Angular 處理變更檢測的方式引起的。
Angular 會定期檢查您的視圖是否與模型中的數據保持同步,並且實際上每秒一次地不斷調用您的getOuputs()
方法!
考慮您的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
不得不等待一個delay
的Observble
時,它不會絕對穩定; 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.