perf(common): AsyncPipe should not call markForCheck on subscription (#54554)

This commit prevents `AsyncPipe` from calling `markForCheck` when values
are synchronously emit during subscription to an observable. This
prevents subscriptions to `Replay` observables from needlessly walking
up to the root of the view tree during template execution for each
new replay observable in the template.

PR Close #54554
This commit is contained in:
Andrew Scott 2024-02-21 16:26:42 -08:00 committed by Dylan Hunn
parent 8e65bdc848
commit 707bfc9b32

View file

@ -102,6 +102,7 @@ const _subscribableStrategy = new SubscribableStrategy();
export class AsyncPipe implements OnDestroy, PipeTransform {
private _ref: ChangeDetectorRef | null;
private _latestValue: any = null;
private markForCheckOnValueUpdate = true;
private _subscription: Unsubscribable | Promise<any> | null = null;
private _obj: Subscribable<any> | Promise<any> | EventEmitter<any> | null = null;
@ -134,7 +135,15 @@ export class AsyncPipe implements OnDestroy, PipeTransform {
transform<T>(obj: Observable<T> | Subscribable<T> | Promise<T> | null | undefined): T | null {
if (!this._obj) {
if (obj) {
this._subscribe(obj);
try {
// Only call `markForCheck` if the value is updated asynchronously.
// Synchronous updates _during_ subscription should not wastefully mark for check -
// this value is already going to be returned from the transform function.
this.markForCheckOnValueUpdate = false;
this._subscribe(obj);
} finally {
this.markForCheckOnValueUpdate = true;
}
}
return this._latestValue;
}
@ -181,9 +190,9 @@ export class AsyncPipe implements OnDestroy, PipeTransform {
private _updateLatestValue(async: any, value: Object): void {
if (async === this._obj) {
this._latestValue = value;
// Note: `this._ref` is only cleared in `ngOnDestroy` so is known to be available when a
// value is being updated.
this._ref!.markForCheck();
if (this.markForCheckOnValueUpdate) {
this._ref?.markForCheck();
}
}
}
}