Skip to content

Commit cea7989

Browse files
authored
fix(cdk/scrolling): Prevent virtual scroll 'flickering' with zoneless (#31316)
* 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 * fixup! fix(cdk/scrolling): Prevent virtual scroll 'flickering' with zoneless * fixup! fix(cdk/scrolling): Prevent virtual scroll 'flickering' with zoneless
1 parent 062c5eb commit cea7989

File tree

1 file changed

+27
-12
lines changed

1 file changed

+27
-12
lines changed

src/cdk/scrolling/virtual-scroll-viewport.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import {ListRange} from '../collections';
1010
import {Platform} from '../platform';
1111
import {
1212
afterNextRender,
13+
ApplicationRef,
1314
booleanAttribute,
1415
ChangeDetectionStrategy,
1516
ChangeDetectorRef,
1617
Component,
18+
DestroyRef,
19+
effect,
1720
ElementRef,
1821
inject,
1922
Inject,
@@ -24,6 +27,7 @@ import {
2427
Optional,
2528
Output,
2629
signal,
30+
untracked,
2731
ViewChild,
2832
ViewEncapsulation,
2933
} from '@angular/core';
@@ -170,8 +174,7 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On
170174
*/
171175
private _renderedContentOffsetNeedsRewrite = false;
172176

173-
/** Whether there is a pending change detection cycle. */
174-
private _isChangeDetectionPending = false;
177+
private _changeDetectionNeeded = signal(false);
175178

176179
/** A list of functions to run after the next change detection cycle. */
177180
private _runAfterChangeDetection: Function[] = [];
@@ -202,6 +205,18 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On
202205
this.elementRef.nativeElement.classList.add('cdk-virtual-scrollable');
203206
this.scrollable = this;
204207
}
208+
209+
const ref = effect(
210+
() => {
211+
if (this._changeDetectionNeeded()) {
212+
this._doChangeDetection();
213+
}
214+
},
215+
// Using ApplicationRef injector is important here because we want this to be a root
216+
// effect that runs before change detection of any application views (since we're depending on markForCheck marking parents dirty)
217+
{injector: inject(ApplicationRef).injector},
218+
);
219+
inject(DestroyRef).onDestroy(() => void ref.destroy());
205220
}
206221

207222
override ngOnInit() {
@@ -488,16 +503,16 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On
488503
this._runAfterChangeDetection.push(runAfter);
489504
}
490505

491-
// Use a Promise to batch together calls to `_doChangeDetection`. This way if we set a bunch of
492-
// properties sequentially we only have to run `_doChangeDetection` once at the end.
493-
if (!this._isChangeDetectionPending) {
494-
this._isChangeDetectionPending = true;
495-
this.ngZone.runOutsideAngular(() =>
496-
Promise.resolve().then(() => {
497-
this._doChangeDetection();
498-
}),
499-
);
506+
if (untracked(this._changeDetectionNeeded)) {
507+
return;
500508
}
509+
this.ngZone.runOutsideAngular(() => {
510+
Promise.resolve().then(() => {
511+
this.ngZone.run(() => {
512+
this._changeDetectionNeeded.set(true);
513+
});
514+
});
515+
});
501516
}
502517

503518
/** Run change detection. */
@@ -520,7 +535,7 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On
520535

521536
afterNextRender(
522537
() => {
523-
this._isChangeDetectionPending = false;
538+
this._changeDetectionNeeded.set(false);
524539
const runAfterChangeDetection = this._runAfterChangeDetection;
525540
this._runAfterChangeDetection = [];
526541
for (const fn of runAfterChangeDetection) {

0 commit comments

Comments
 (0)