From d4504331ff0ff45831a41e387fa32462b9d39c82 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Mon, 9 Jun 2025 12:34:43 -0700 Subject: [PATCH] fix(cdk/scrolling): Prevent virtual scroll 'flickering' with zoneless This commit reworks the change detection coalescing to use signal and effects, which ensures the transform and the afterNextRender are applied within the same application tick without any awkward workarounds or fiddling with being inside or outside the zone --- src/cdk/scrolling/virtual-scroll-viewport.ts | 30 +++++++++++--------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/cdk/scrolling/virtual-scroll-viewport.ts b/src/cdk/scrolling/virtual-scroll-viewport.ts index 693c3cd92553..8981e2788f1f 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, @@ -170,8 +173,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 +204,17 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On this.elementRef.nativeElement.classList.add('cdk-virtual-scrollable'); this.scrollable = this; } + + const ref = effect( + () => { + if (!this._changeDetectionNeeded()) { + return; + } + this._doChangeDetection(); + }, + {injector: inject(ApplicationRef).injector}, + ); + inject(DestroyRef).onDestroy(() => void ref.destroy()); } override ngOnInit() { @@ -488,16 +501,7 @@ 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(); - }), - ); - } + this._changeDetectionNeeded.set(true); } /** Run change detection. */ @@ -520,7 +524,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) {