@@ -48,7 +48,7 @@ function rangesEqual(r1: ListRange, r2: ListRange): boolean {
48
48
encapsulation : ViewEncapsulation . None ,
49
49
changeDetection : ChangeDetectionStrategy . OnPush ,
50
50
} )
51
- export class CdkVirtualScrollViewport implements DoCheck , OnInit , OnDestroy {
51
+ export class CdkVirtualScrollViewport implements OnInit , OnDestroy {
52
52
/** Emits when the viewport is detached from a CdkVirtualForOf. */
53
53
private _detachedSubject = new Subject < void > ( ) ;
54
54
@@ -102,37 +102,29 @@ export class CdkVirtualScrollViewport implements DoCheck, OnInit, OnDestroy {
102
102
/** Observable that emits when the viewport is destroyed. */
103
103
private _destroyed = new Subject < void > ( ) ;
104
104
105
+ /** Whether there is a pending change detection cycle. */
106
+ private _checkPending = false ;
107
+
108
+ /** A list of functions to run after the next change detection cycle. */
109
+ private _runAfterCheck : Function [ ] = [ ] ;
110
+
105
111
constructor ( public elementRef : ElementRef , private _changeDetectorRef : ChangeDetectorRef ,
106
112
private _ngZone : NgZone , private _sanitizer : DomSanitizer ,
107
113
@Inject ( VIRTUAL_SCROLL_STRATEGY ) private _scrollStrategy : VirtualScrollStrategy ) { }
108
114
109
115
ngOnInit ( ) {
110
116
// It's still too early to measure the viewport at this point. Deferring with a promise allows
111
117
// the Viewport to be rendered with the correct size before we measure.
112
- Promise . resolve ( ) . then ( ( ) => {
118
+ this . _ngZone . runOutsideAngular ( ( ) => Promise . resolve ( ) . then ( ( ) => {
113
119
this . _measureViewportSize ( ) ;
114
120
this . _scrollStrategy . attach ( this ) ;
115
121
116
- this . _ngZone . runOutsideAngular ( ( ) => {
117
- fromEvent ( this . elementRef . nativeElement , 'scroll' )
118
- // Sample the scroll stream at every animation frame. This way if there are multiple
119
- // scroll events in the same frame we only need to recheck our layout once.
120
- . pipe ( sampleTime ( 0 , animationFrameScheduler ) , takeUntil ( this . _destroyed ) )
121
- . subscribe ( ( ) => this . _scrollStrategy . onContentScrolled ( ) ) ;
122
- } ) ;
123
- } ) ;
124
- }
125
-
126
- ngDoCheck ( ) {
127
- // In order to batch setting the scroll offset together with other DOM writes, we wait until a
128
- // change detection cycle to actually apply it.
129
- if ( this . _pendingScrollOffset != null ) {
130
- if ( this . orientation === 'horizontal' ) {
131
- this . elementRef . nativeElement . scrollLeft = this . _pendingScrollOffset ;
132
- } else {
133
- this . elementRef . nativeElement . scrollTop = this . _pendingScrollOffset ;
134
- }
135
- }
122
+ fromEvent ( this . elementRef . nativeElement , 'scroll' )
123
+ // Sample the scroll stream at every animation frame. This way if there are multiple
124
+ // scroll events in the same frame we only need to recheck our layout once.
125
+ . pipe ( sampleTime ( 0 , animationFrameScheduler ) , takeUntil ( this . _destroyed ) )
126
+ . subscribe ( ( ) => this . _scrollStrategy . onContentScrolled ( ) ) ;
127
+ } ) ) ;
136
128
}
137
129
138
130
ngOnDestroy ( ) {
@@ -151,16 +143,18 @@ export class CdkVirtualScrollViewport implements DoCheck, OnInit, OnDestroy {
151
143
if ( this . _forOf ) {
152
144
throw Error ( 'CdkVirtualScrollViewport is already attached.' ) ;
153
145
}
154
- this . _forOf = forOf ;
155
146
156
147
// Subscribe to the data stream of the CdkVirtualForOf to keep track of when the data length
157
148
// changes.
158
- this . _forOf . dataStream . pipe ( takeUntil ( this . _detachedSubject ) ) . subscribe ( data => {
159
- const len = data . length ;
160
- if ( len !== this . _dataLength ) {
161
- this . _dataLength = len ;
162
- this . _scrollStrategy . onDataLengthChanged ( ) ;
163
- }
149
+ this . _ngZone . runOutsideAngular ( ( ) => {
150
+ this . _forOf = forOf ;
151
+ this . _forOf . dataStream . pipe ( takeUntil ( this . _detachedSubject ) ) . subscribe ( data => {
152
+ const len = data . length ;
153
+ if ( len !== this . _dataLength ) {
154
+ this . _dataLength = len ;
155
+ this . _scrollStrategy . onDataLengthChanged ( ) ;
156
+ }
157
+ } ) ;
164
158
} ) ;
165
159
}
166
160
@@ -190,40 +184,22 @@ export class CdkVirtualScrollViewport implements DoCheck, OnInit, OnDestroy {
190
184
return this . _renderedRange ;
191
185
}
192
186
193
- // TODO(mmalebra): Consider calling `detectChanges()` directly rather than the methods below.
194
-
195
187
/**
196
188
* Sets the total size of all content (in pixels), including content that is not currently
197
189
* rendered.
198
190
*/
199
191
setTotalContentSize ( size : number ) {
200
192
if ( this . _totalContentSize !== size ) {
201
- // Re-enter the Angular zone so we can mark for change detection.
202
- this . _ngZone . run ( ( ) => {
203
- this . _totalContentSize = size ;
204
- this . _changeDetectorRef . markForCheck ( ) ;
205
- } ) ;
193
+ this . _totalContentSize = size ;
194
+ this . _markForCheck ( ) ;
206
195
}
207
196
}
208
197
209
198
/** Sets the currently rendered range of indices. */
210
199
setRenderedRange ( range : ListRange ) {
211
200
if ( ! rangesEqual ( this . _renderedRange , range ) ) {
212
- // Re-enter the Angular zone so we can mark for change detection.
213
- this . _ngZone . run ( ( ) => {
214
- this . _renderedRangeSubject . next ( this . _renderedRange = range ) ;
215
- this . _changeDetectorRef . markForCheck ( ) ;
216
- } ) ;
217
- // Queue this up in a `Promise.resolve()` so that if the user makes a series of calls
218
- // like:
219
- //
220
- // viewport.setRenderedRange(...);
221
- // viewport.setTotalContentSize(...);
222
- // viewport.setRenderedContentOffset(...);
223
- //
224
- // The call to `onContentRendered` will happen after all of the updates have been applied.
225
- this . _ngZone . runOutsideAngular ( ( ) => this . _ngZone . onStable . pipe ( take ( 1 ) ) . subscribe (
226
- ( ) => Promise . resolve ( ) . then ( ( ) => this . _scrollStrategy . onContentRendered ( ) ) ) ) ;
201
+ this . _renderedRangeSubject . next ( this . _renderedRange = range ) ;
202
+ this . _markForCheck ( ( ) => this . _scrollStrategy . onContentRendered ( ) ) ;
227
203
}
228
204
}
229
205
@@ -250,25 +226,18 @@ export class CdkVirtualScrollViewport implements DoCheck, OnInit, OnDestroy {
250
226
this . _renderedContentOffsetNeedsRewrite = true ;
251
227
}
252
228
if ( this . _rawRenderedContentTransform != transform ) {
253
- // Re-enter the Angular zone so we can mark for change detection.
254
- this . _ngZone . run ( ( ) => {
255
- // We know this value is safe because we parse `offset` with `Number()` before passing it
256
- // into the string.
257
- this . _rawRenderedContentTransform = transform ;
258
- this . _renderedContentTransform = this . _sanitizer . bypassSecurityTrustStyle ( transform ) ;
259
- this . _changeDetectorRef . markForCheck ( ) ;
260
-
261
- // If the rendered content offset was specified as an offset to the end of the content,
262
- // rewrite it as an offset to the start of the content.
263
- this . _ngZone . onStable . pipe ( take ( 1 ) ) . subscribe ( ( ) => {
264
- if ( this . _renderedContentOffsetNeedsRewrite ) {
265
- this . _renderedContentOffset -= this . measureRenderedContentSize ( ) ;
266
- this . _renderedContentOffsetNeedsRewrite = false ;
267
- this . setRenderedContentOffset ( this . _renderedContentOffset ) ;
268
- } else {
269
- this . _scrollStrategy . onRenderedOffsetChanged ( ) ;
270
- }
271
- } ) ;
229
+ // We know this value is safe because we parse `offset` with `Number()` before passing it
230
+ // into the string.
231
+ this . _rawRenderedContentTransform = transform ;
232
+ this . _renderedContentTransform = this . _sanitizer . bypassSecurityTrustStyle ( transform ) ;
233
+ this . _markForCheck ( ( ) => {
234
+ if ( this . _renderedContentOffsetNeedsRewrite ) {
235
+ this . _renderedContentOffset -= this . measureRenderedContentSize ( ) ;
236
+ this . _renderedContentOffsetNeedsRewrite = false ;
237
+ this . setRenderedContentOffset ( this . _renderedContentOffset ) ;
238
+ } else {
239
+ this . _scrollStrategy . onRenderedOffsetChanged ( ) ;
240
+ }
272
241
} ) ;
273
242
}
274
243
}
@@ -277,10 +246,8 @@ export class CdkVirtualScrollViewport implements DoCheck, OnInit, OnDestroy {
277
246
setScrollOffset ( offset : number ) {
278
247
// Rather than setting the offset immediately, we batch it up to be applied along with other DOM
279
248
// writes during the next change detection cycle.
280
- this . _ngZone . run ( ( ) => {
281
- this . _pendingScrollOffset = offset ;
282
- this . _changeDetectorRef . markForCheck ( ) ;
283
- } ) ;
249
+ this . _pendingScrollOffset = offset ;
250
+ this . _markForCheck ( ) ;
284
251
}
285
252
286
253
/** Gets the current scroll offset of the viewport (in pixels). */
@@ -319,4 +286,40 @@ export class CdkVirtualScrollViewport implements DoCheck, OnInit, OnDestroy {
319
286
this . _viewportSize = this . orientation === 'horizontal' ?
320
287
viewportEl . clientWidth : viewportEl . clientHeight ;
321
288
}
289
+
290
+ /** Queue up change detection to run. */
291
+ private _markForCheck ( runAfter ?: Function ) {
292
+ if ( runAfter ) {
293
+ this . _runAfterCheck . push ( runAfter ) ;
294
+ }
295
+ if ( ! this . _checkPending ) {
296
+ this . _checkPending = true ;
297
+ this . _ngZone . runOutsideAngular ( ( ) => Promise . resolve ( ) . then ( ( ) => {
298
+ if ( this . _ngZone . isStable ) {
299
+ this . _doCheck ( ) ;
300
+ } else {
301
+ this . _ngZone . onStable . pipe ( take ( 1 ) ) . subscribe ( ( ) => this . _doCheck ( ) ) ;
302
+ }
303
+ } ) ) ;
304
+ }
305
+ }
306
+
307
+ /** Run change detection. */
308
+ private _doCheck ( ) {
309
+ this . _checkPending = false ;
310
+ this . _ngZone . run ( ( ) => this . _changeDetectorRef . detectChanges ( ) ) ;
311
+ // In order to batch setting the scroll offset together with other DOM writes, we wait until a
312
+ // change detection cycle to actually apply it.
313
+ if ( this . _pendingScrollOffset != null ) {
314
+ if ( this . orientation === 'horizontal' ) {
315
+ this . elementRef . nativeElement . scrollLeft = this . _pendingScrollOffset ;
316
+ } else {
317
+ this . elementRef . nativeElement . scrollTop = this . _pendingScrollOffset ;
318
+ }
319
+ }
320
+ for ( let fn of this . _runAfterCheck ) {
321
+ fn ( ) ;
322
+ }
323
+ this . _runAfterCheck = [ ] ;
324
+ }
322
325
}
0 commit comments