@@ -45,12 +45,6 @@ export type CdkVirtualForOfContext<T> = {
45
45
} ;
46
46
47
47
48
- type RecordViewTuple < T > = {
49
- record : IterableChangeRecord < T > | null ,
50
- view ?: EmbeddedViewRef < CdkVirtualForOfContext < T > >
51
- } ;
52
-
53
-
54
48
/**
55
49
* A directive similar to `ngForOf` to be used for rendering data inside a virtual scrolling
56
50
* container.
@@ -101,6 +95,8 @@ export class CdkVirtualForOf<T> implements CollectionViewer, DoCheck, OnDestroy
101
95
}
102
96
}
103
97
98
+ @Input ( ) cdkVirtualForTemplateCacheSize : number = 20 ;
99
+
104
100
/** Emits whenever the data in the current DataSource changes. */
105
101
dataStream : Observable < T [ ] > = this . _dataSourceChanges
106
102
. pipe (
@@ -256,82 +252,64 @@ export class CdkVirtualForOf<T> implements CollectionViewer, DoCheck, OnDestroy
256
252
257
253
/** Apply changes to the DOM. */
258
254
private _applyChanges ( changes : IterableChanges < T > ) {
259
- // TODO(mmalerba): Currently we remove every view and then re-insert it in the correct place.
260
- // It would be better to generate the minimal set of remove & inserts to get to the new list
261
- // instead.
262
-
263
- // Detach all of the views and add them into an array to preserve their original order.
264
- const previousViews : ( EmbeddedViewRef < CdkVirtualForOfContext < T > > | null ) [ ] = [ ] ;
265
- let i = this . _viewContainerRef . length ;
266
- while ( i -- ) {
267
- previousViews . unshift (
268
- this . _viewContainerRef . detach ( ) ! as EmbeddedViewRef < CdkVirtualForOfContext < T > > ) ;
269
- }
270
-
271
- // Mark the removed indices so we can recycle their views.
272
- changes . forEachRemovedItem ( record => {
273
- this . _templateCache . push ( previousViews [ record . previousIndex ! ] ! ) ;
274
- previousViews [ record . previousIndex ! ] = null ;
275
- } ) ;
276
-
277
- // Queue up the newly added items to be inserted, recycling views from the cache if possible.
278
- const insertTuples : RecordViewTuple < T > [ ] = [ ] ;
279
- changes . forEachAddedItem ( record => {
280
- insertTuples [ record . currentIndex ! ] = { record, view : this . _templateCache . pop ( ) } ;
281
- } ) ;
282
-
283
- // Queue up moved items to be re-inserted.
284
- changes . forEachMovedItem ( record => {
285
- insertTuples [ record . currentIndex ! ] = { record, view : previousViews [ record . previousIndex ! ] ! } ;
286
- previousViews [ record . previousIndex ! ] = null ;
255
+ // Rearrange the views to put them in the right location.
256
+ changes . forEachOperation (
257
+ ( record : IterableChangeRecord < T > , adjustedPreviousIndex : number , currentIndex : number ) => {
258
+ if ( record . previousIndex == null ) { // Item added.
259
+ const view = this . _getViewForNewItem ( ) ;
260
+ this . _viewContainerRef . insert ( view , currentIndex ) ;
261
+ view . context . $implicit = record . item ;
262
+ } else if ( currentIndex == null ) { // Item removed.
263
+ this . _cacheView ( this . _viewContainerRef . detach ( adjustedPreviousIndex ) as
264
+ EmbeddedViewRef < CdkVirtualForOfContext < T > > ) ;
265
+ } else { // Item moved.
266
+ const view = this . _viewContainerRef . get ( adjustedPreviousIndex ) as
267
+ EmbeddedViewRef < CdkVirtualForOfContext < T > > ;
268
+ this . _viewContainerRef . move ( view , currentIndex ) ;
269
+ view . context . $implicit = record . item ;
270
+ }
271
+ } ) ;
272
+
273
+ // Update $implicit for any items that had an identity change.
274
+ changes . forEachIdentityChange ( ( record : any ) => {
275
+ const view = this . _viewContainerRef . get ( record . currentIndex ) as
276
+ EmbeddedViewRef < CdkVirtualForOfContext < T > > ;
277
+ view . context . $implicit = record . item ;
287
278
} ) ;
288
279
289
- // We have nulled-out all of the views that were removed or moved from previousViews. What is
290
- // left is the unchanged items that we queue up to be re-inserted.
291
- i = previousViews . length ;
280
+ // Update the context variables on all items.
281
+ let i = this . _viewContainerRef . length , count = this . _data . length ;
292
282
while ( i -- ) {
293
- if ( previousViews [ i ] ) {
294
- insertTuples [ i ] = { record : null , view : previousViews [ i ] ! } ;
295
- }
283
+ const view = this . _viewContainerRef . get ( i ) as EmbeddedViewRef < CdkVirtualForOfContext < T > > ;
284
+ view . context . index = this . _renderedRange . start + i ;
285
+ view . context . count = count ;
286
+ this . _updateComputedContextProperties ( view . context ) ;
296
287
}
297
-
298
- // We now have a full list of everything to be inserted, so go ahead and insert them.
299
- this . _insertViews ( insertTuples ) ;
300
288
}
301
289
302
- /** Insert the RecordViewTuples into the container element. */
303
- private _insertViews ( insertTuples : RecordViewTuple < T > [ ] ) {
304
- const count = this . _data . length ;
305
- let i = insertTuples . length ;
306
- const lastIndex = i - 1 ;
307
- while ( i -- ) {
308
- const index = lastIndex - i ;
309
- let { view, record} = insertTuples [ index ] ;
310
- if ( view ) {
311
- this . _viewContainerRef . insert ( view ) ;
312
- } else {
313
- view = this . _viewContainerRef . createEmbeddedView ( this . _template , {
314
- $implicit : null ! ,
315
- cdkVirtualForOf : this . _cdkVirtualForOf ,
316
- index : - 1 ,
317
- count : - 1 ,
318
- first : false ,
319
- last : false ,
320
- odd : false ,
321
- even : false
322
- } ) ;
323
- }
324
-
325
- if ( record ) {
326
- view . context . $implicit = record . item as T ;
327
- }
328
- view . context . index = this . _renderedRange . start + index ;
329
- view . context . count = count ;
330
- this . _updateComputedContextProperties ( view . context ) ;
331
- view . detectChanges ( ) ;
290
+ /** Cache the given detached view. */
291
+ private _cacheView ( view : EmbeddedViewRef < CdkVirtualForOfContext < T > > ) {
292
+ if ( this . _templateCache . length < this . cdkVirtualForTemplateCacheSize ) {
293
+ this . _templateCache . push ( view ) ;
294
+ } else {
295
+ view . destroy ( ) ;
332
296
}
333
297
}
334
298
299
+ /** Get a view for a new item, either from the cache or by creating a new one. */
300
+ private _getViewForNewItem ( ) : EmbeddedViewRef < CdkVirtualForOfContext < T > > {
301
+ return this . _templateCache . pop ( ) || this . _viewContainerRef . createEmbeddedView ( this . _template , {
302
+ $implicit : null ! ,
303
+ cdkVirtualForOf : this . _cdkVirtualForOf ,
304
+ index : - 1 ,
305
+ count : - 1 ,
306
+ first : false ,
307
+ last : false ,
308
+ odd : false ,
309
+ even : false
310
+ } ) ;
311
+ }
312
+
335
313
/** Update the computed properties on the `CdkVirtualForOfContext`. */
336
314
private _updateComputedContextProperties ( context : CdkVirtualForOfContext < any > ) {
337
315
context . first = context . index === 0 ;
0 commit comments