@@ -102,18 +102,20 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
102
102
private _destroyed = new Subject < void > ( ) ;
103
103
104
104
/** Whether there is a pending change detection cycle. */
105
- private _checkPending = false ;
105
+ private _isChangeDetectionPending = false ;
106
106
107
107
/** A list of functions to run after the next change detection cycle. */
108
- private _runAfterCheck : Function [ ] = [ ] ;
108
+ private _runAfterChangeDetection : Function [ ] = [ ] ;
109
109
110
110
constructor ( public elementRef : ElementRef , private _changeDetectorRef : ChangeDetectorRef ,
111
111
private _ngZone : NgZone , private _sanitizer : DomSanitizer ,
112
112
@Inject ( VIRTUAL_SCROLL_STRATEGY ) private _scrollStrategy : VirtualScrollStrategy ) { }
113
113
114
114
ngOnInit ( ) {
115
115
// 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.
117
119
this . _ngZone . runOutsideAngular ( ( ) => Promise . resolve ( ) . then ( ( ) => {
118
120
this . _measureViewportSize ( ) ;
119
121
this . _scrollStrategy . attach ( this ) ;
@@ -124,7 +126,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
124
126
. pipe ( sampleTime ( 0 , animationFrameScheduler ) , takeUntil ( this . _destroyed ) )
125
127
. subscribe ( ( ) => this . _scrollStrategy . onContentScrolled ( ) ) ;
126
128
127
- this . _markForCheck ( ) ;
129
+ this . _markChangeDetectionNeeded ( ) ;
128
130
} ) ) ;
129
131
}
130
132
@@ -146,13 +148,14 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
146
148
}
147
149
148
150
// 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.
150
153
this . _ngZone . runOutsideAngular ( ( ) => {
151
154
this . _forOf = forOf ;
152
155
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 ;
156
159
this . _scrollStrategy . onDataLengthChanged ( ) ;
157
160
}
158
161
} ) ;
@@ -192,15 +195,15 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
192
195
setTotalContentSize ( size : number ) {
193
196
if ( this . _totalContentSize !== size ) {
194
197
this . _totalContentSize = size ;
195
- this . _markForCheck ( ) ;
198
+ this . _markChangeDetectionNeeded ( ) ;
196
199
}
197
200
}
198
201
199
202
/** Sets the currently rendered range of indices. */
200
203
setRenderedRange ( range : ListRange ) {
201
204
if ( ! rangesEqual ( this . _renderedRange , range ) ) {
202
205
this . _renderedRangeSubject . next ( this . _renderedRange = range ) ;
203
- this . _markForCheck ( ( ) => this . _scrollStrategy . onContentRendered ( ) ) ;
206
+ this . _markChangeDetectionNeeded ( ( ) => this . _scrollStrategy . onContentRendered ( ) ) ;
204
207
}
205
208
}
206
209
@@ -231,7 +234,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
231
234
// into the string.
232
235
this . _rawRenderedContentTransform = transform ;
233
236
this . _renderedContentTransform = this . _sanitizer . bypassSecurityTrustStyle ( transform ) ;
234
- this . _markForCheck ( ( ) => {
237
+ this . _markChangeDetectionNeeded ( ( ) => {
235
238
if ( this . _renderedContentOffsetNeedsRewrite ) {
236
239
this . _renderedContentOffset -= this . measureRenderedContentSize ( ) ;
237
240
this . _renderedContentOffsetNeedsRewrite = false ;
@@ -248,7 +251,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
248
251
// Rather than setting the offset immediately, we batch it up to be applied along with other DOM
249
252
// writes during the next change detection cycle.
250
253
this . _pendingScrollOffset = offset ;
251
- this . _markForCheck ( ) ;
254
+ this . _markChangeDetectionNeeded ( ) ;
252
255
}
253
256
254
257
/** Gets the current scroll offset of the viewport (in pixels). */
@@ -289,38 +292,43 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
289
292
}
290
293
291
294
/** Queue up change detection to run. */
292
- private _markForCheck ( runAfter ?: Function ) {
295
+ private _markChangeDetectionNeeded ( runAfter ?: Function ) {
293
296
if ( runAfter ) {
294
- this . _runAfterCheck . push ( runAfter ) ;
297
+ this . _runAfterChangeDetection . push ( runAfter ) ;
295
298
}
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 ;
298
304
this . _ngZone . runOutsideAngular ( ( ) => Promise . resolve ( ) . then ( ( ) => {
299
305
if ( this . _ngZone . isStable ) {
300
- this . _doCheck ( ) ;
306
+ this . _doChangeDetection ( ) ;
301
307
} else {
302
- this . _ngZone . onStable . pipe ( take ( 1 ) ) . subscribe ( ( ) => this . _doCheck ( ) ) ;
308
+ this . _ngZone . onStable . pipe ( take ( 1 ) ) . subscribe ( ( ) => this . _doChangeDetection ( ) ) ;
303
309
}
304
310
} ) ) ;
305
311
}
306
312
}
307
313
308
314
/** Run change detection. */
309
- private _doCheck ( ) {
310
- this . _checkPending = false ;
315
+ private _doChangeDetection ( ) {
316
+ this . _isChangeDetectionPending = false ;
317
+
318
+ // Apply changes to Angular bindings.
311
319
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.
314
321
if ( this . _pendingScrollOffset != null ) {
315
322
if ( this . orientation === 'horizontal' ) {
316
323
this . elementRef . nativeElement . scrollLeft = this . _pendingScrollOffset ;
317
324
} else {
318
325
this . elementRef . nativeElement . scrollTop = this . _pendingScrollOffset ;
319
326
}
320
327
}
321
- for ( let fn of this . _runAfterCheck ) {
328
+
329
+ for ( let fn of this . _runAfterChangeDetection ) {
322
330
fn ( ) ;
323
331
}
324
- this . _runAfterCheck = [ ] ;
332
+ this . _runAfterChangeDetection = [ ] ;
325
333
}
326
334
}
0 commit comments