diff --git a/src/cdk/scrolling/virtual-scroll-viewport.ts b/src/cdk/scrolling/virtual-scroll-viewport.ts index 693c3cd92553..d37e3675607d 100644 --- a/src/cdk/scrolling/virtual-scroll-viewport.ts +++ b/src/cdk/scrolling/virtual-scroll-viewport.ts @@ -10,10 +10,13 @@ import {ListRange} from '../collections'; import {Platform} from '../platform'; import { afterNextRender, + ApplicationRef, booleanAttribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, + effect, ElementRef, inject, Inject, @@ -24,6 +27,7 @@ import { Optional, Output, signal, + untracked, ViewChild, ViewEncapsulation, } from '@angular/core'; @@ -170,8 +174,7 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On */ private _renderedContentOffsetNeedsRewrite = false; - /** Whether there is a pending change detection cycle. */ - private _isChangeDetectionPending = false; + private _changeDetectionNeeded = signal(false); /** A list of functions to run after the next change detection cycle. */ private _runAfterChangeDetection: Function[] = []; @@ -202,6 +205,18 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On this.elementRef.nativeElement.classList.add('cdk-virtual-scrollable'); this.scrollable = this; } + + const ref = effect( + () => { + if (this._changeDetectionNeeded()) { + this._doChangeDetection(); + } + }, + // Using ApplicationRef injector is important here because we want this to be a root + // effect that runs before change detection of any application views (since we're depending on markForCheck marking parents dirty) + {injector: inject(ApplicationRef).injector}, + ); + inject(DestroyRef).onDestroy(() => void ref.destroy()); } override ngOnInit() { @@ -488,16 +503,16 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On this._runAfterChangeDetection.push(runAfter); } - // Use a Promise to batch together calls to `_doChangeDetection`. This way if we set a bunch of - // properties sequentially we only have to run `_doChangeDetection` once at the end. - if (!this._isChangeDetectionPending) { - this._isChangeDetectionPending = true; - this.ngZone.runOutsideAngular(() => - Promise.resolve().then(() => { - this._doChangeDetection(); - }), - ); + if (untracked(this._changeDetectionNeeded)) { + return; } + this.ngZone.runOutsideAngular(() => { + Promise.resolve().then(() => { + this.ngZone.run(() => { + this._changeDetectionNeeded.set(true); + }); + }); + }); } /** Run change detection. */ @@ -520,7 +535,7 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On afterNextRender( () => { - this._isChangeDetectionPending = false; + this._changeDetectionNeeded.set(false); const runAfterChangeDetection = this._runAfterChangeDetection; this._runAfterChangeDetection = []; for (const fn of runAfterChangeDetection) {