@@ -102,18 +102,20 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
102102 private _destroyed = new Subject < void > ( ) ;
103103
104104 /** Whether there is a pending change detection cycle. */
105- private _checkPending = false ;
105+ private _isChangeDetectionPending = false ;
106106
107107 /** A list of functions to run after the next change detection cycle. */
108- private _runAfterCheck : Function [ ] = [ ] ;
108+ private _runAfterChangeDetection : Function [ ] = [ ] ;
109109
110110 constructor ( public elementRef : ElementRef , private _changeDetectorRef : ChangeDetectorRef ,
111111 private _ngZone : NgZone , private _sanitizer : DomSanitizer ,
112112 @Inject ( VIRTUAL_SCROLL_STRATEGY ) private _scrollStrategy : VirtualScrollStrategy ) { }
113113
114114 ngOnInit ( ) {
115115 // It's still too early to measure the viewport at this point. Deferring with a promise allows
116- // the Viewport to be rendered with the correct size before we measure.
116+ // the Viewport to be rendered with the correct size before we measure. We run this outside the
117+ // zone to avoid causing more change detection cycles. We handle the change detection loop
118+ // ourselves instead.
117119 this . _ngZone . runOutsideAngular ( ( ) => Promise . resolve ( ) . then ( ( ) => {
118120 this . _measureViewportSize ( ) ;
119121 this . _scrollStrategy . attach ( this ) ;
@@ -124,7 +126,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
124126 . pipe ( sampleTime ( 0 , animationFrameScheduler ) , takeUntil ( this . _destroyed ) )
125127 . subscribe ( ( ) => this . _scrollStrategy . onContentScrolled ( ) ) ;
126128
127- this . _markForCheck ( ) ;
129+ this . _markChangeDetectionNeeded ( ) ;
128130 } ) ) ;
129131 }
130132
@@ -146,13 +148,14 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
146148 }
147149
148150 // Subscribe to the data stream of the CdkVirtualForOf to keep track of when the data length
149- // changes.
151+ // changes. Run outside the zone to avoid triggering change detection, since we're managing the
152+ // change detection loop ourselves.
150153 this . _ngZone . runOutsideAngular ( ( ) => {
151154 this . _forOf = forOf ;
152155 this . _forOf . dataStream . pipe ( takeUntil ( this . _detachedSubject ) ) . subscribe ( data => {
153- const len = data . length ;
154- if ( len !== this . _dataLength ) {
155- this . _dataLength = len ;
156+ const newLength = data . length ;
157+ if ( newLength !== this . _dataLength ) {
158+ this . _dataLength = newLength ;
156159 this . _scrollStrategy . onDataLengthChanged ( ) ;
157160 }
158161 } ) ;
@@ -192,15 +195,15 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
192195 setTotalContentSize ( size : number ) {
193196 if ( this . _totalContentSize !== size ) {
194197 this . _totalContentSize = size ;
195- this . _markForCheck ( ) ;
198+ this . _markChangeDetectionNeeded ( ) ;
196199 }
197200 }
198201
199202 /** Sets the currently rendered range of indices. */
200203 setRenderedRange ( range : ListRange ) {
201204 if ( ! rangesEqual ( this . _renderedRange , range ) ) {
202205 this . _renderedRangeSubject . next ( this . _renderedRange = range ) ;
203- this . _markForCheck ( ( ) => this . _scrollStrategy . onContentRendered ( ) ) ;
206+ this . _markChangeDetectionNeeded ( ( ) => this . _scrollStrategy . onContentRendered ( ) ) ;
204207 }
205208 }
206209
@@ -231,7 +234,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
231234 // into the string.
232235 this . _rawRenderedContentTransform = transform ;
233236 this . _renderedContentTransform = this . _sanitizer . bypassSecurityTrustStyle ( transform ) ;
234- this . _markForCheck ( ( ) => {
237+ this . _markChangeDetectionNeeded ( ( ) => {
235238 if ( this . _renderedContentOffsetNeedsRewrite ) {
236239 this . _renderedContentOffset -= this . measureRenderedContentSize ( ) ;
237240 this . _renderedContentOffsetNeedsRewrite = false ;
@@ -248,7 +251,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
248251 // Rather than setting the offset immediately, we batch it up to be applied along with other DOM
249252 // writes during the next change detection cycle.
250253 this . _pendingScrollOffset = offset ;
251- this . _markForCheck ( ) ;
254+ this . _markChangeDetectionNeeded ( ) ;
252255 }
253256
254257 /** Gets the current scroll offset of the viewport (in pixels). */
@@ -289,38 +292,43 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
289292 }
290293
291294 /** Queue up change detection to run. */
292- private _markForCheck ( runAfter ?: Function ) {
295+ private _markChangeDetectionNeeded ( runAfter ?: Function ) {
293296 if ( runAfter ) {
294- this . _runAfterCheck . push ( runAfter ) ;
297+ this . _runAfterChangeDetection . push ( runAfter ) ;
295298 }
296- if ( ! this . _checkPending ) {
297- this . _checkPending = true ;
299+
300+ // Use a Promise to batch together calls to `_doChangeDetection`. This way if we set a bunch of
301+ // properties sequentially we only have to run `_doChangeDetection` once at the end.
302+ if ( ! this . _isChangeDetectionPending ) {
303+ this . _isChangeDetectionPending = true ;
298304 this . _ngZone . runOutsideAngular ( ( ) => Promise . resolve ( ) . then ( ( ) => {
299305 if ( this . _ngZone . isStable ) {
300- this . _doCheck ( ) ;
306+ this . _doChangeDetection ( ) ;
301307 } else {
302- this . _ngZone . onStable . pipe ( take ( 1 ) ) . subscribe ( ( ) => this . _doCheck ( ) ) ;
308+ this . _ngZone . onStable . pipe ( take ( 1 ) ) . subscribe ( ( ) => this . _doChangeDetection ( ) ) ;
303309 }
304310 } ) ) ;
305311 }
306312 }
307313
308314 /** Run change detection. */
309- private _doCheck ( ) {
310- this . _checkPending = false ;
315+ private _doChangeDetection ( ) {
316+ this . _isChangeDetectionPending = false ;
317+
318+ // Apply changes to Angular bindings.
311319 this . _ngZone . run ( ( ) => this . _changeDetectorRef . detectChanges ( ) ) ;
312- // In order to batch setting the scroll offset together with other DOM writes, we wait until a
313- // change detection cycle to actually apply it.
320+ // Apply the pending scroll offset separately, since it can't be set up as an Angular binding.
314321 if ( this . _pendingScrollOffset != null ) {
315322 if ( this . orientation === 'horizontal' ) {
316323 this . elementRef . nativeElement . scrollLeft = this . _pendingScrollOffset ;
317324 } else {
318325 this . elementRef . nativeElement . scrollTop = this . _pendingScrollOffset ;
319326 }
320327 }
321- for ( let fn of this . _runAfterCheck ) {
328+
329+ for ( let fn of this . _runAfterChangeDetection ) {
322330 fn ( ) ;
323331 }
324- this . _runAfterCheck = [ ] ;
332+ this . _runAfterChangeDetection = [ ] ;
325333 }
326334}
0 commit comments