Skip to content

Commit d450433

Browse files
committed
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
1 parent 8cb522d commit d450433

File tree

1 file changed

+17
-13
lines changed

1 file changed

+17
-13
lines changed

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

Lines changed: 17 additions & 13 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,
@@ -170,8 +173,7 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On
170173
*/
171174
private _renderedContentOffsetNeedsRewrite = false;
172175

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

176178
/** A list of functions to run after the next change detection cycle. */
177179
private _runAfterChangeDetection: Function[] = [];
@@ -202,6 +204,17 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On
202204
this.elementRef.nativeElement.classList.add('cdk-virtual-scrollable');
203205
this.scrollable = this;
204206
}
207+
208+
const ref = effect(
209+
() => {
210+
if (!this._changeDetectionNeeded()) {
211+
return;
212+
}
213+
this._doChangeDetection();
214+
},
215+
{injector: inject(ApplicationRef).injector},
216+
);
217+
inject(DestroyRef).onDestroy(() => void ref.destroy());
205218
}
206219

207220
override ngOnInit() {
@@ -488,16 +501,7 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On
488501
this._runAfterChangeDetection.push(runAfter);
489502
}
490503

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-
);
500-
}
504+
this._changeDetectionNeeded.set(true);
501505
}
502506

503507
/** Run change detection. */
@@ -520,7 +524,7 @@ export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements On
520524

521525
afterNextRender(
522526
() => {
523-
this._isChangeDetectionPending = false;
527+
this._changeDetectionNeeded.set(false);
524528
const runAfterChangeDetection = this._runAfterChangeDetection;
525529
this._runAfterChangeDetection = [];
526530
for (const fn of runAfterChangeDetection) {

0 commit comments

Comments
 (0)