Skip to content

Commit 7490856

Browse files
committed
virtual-scroll: only move views that need to be moved
1 parent 57576eb commit 7490856

File tree

3 files changed

+56
-79
lines changed

3 files changed

+56
-79
lines changed

src/cdk-experimental/scrolling/virtual-for-of.ts

Lines changed: 51 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,6 @@ export type CdkVirtualForOfContext<T> = {
4545
};
4646

4747

48-
type RecordViewTuple<T> = {
49-
record: IterableChangeRecord<T> | null,
50-
view?: EmbeddedViewRef<CdkVirtualForOfContext<T>>
51-
};
52-
53-
5448
/**
5549
* A directive similar to `ngForOf` to be used for rendering data inside a virtual scrolling
5650
* container.
@@ -101,6 +95,8 @@ export class CdkVirtualForOf<T> implements CollectionViewer, DoCheck, OnDestroy
10195
}
10296
}
10397

98+
@Input() cdkVirtualForTemplateCacheSize: number = 20;
99+
104100
/** Emits whenever the data in the current DataSource changes. */
105101
dataStream: Observable<T[]> = this._dataSourceChanges
106102
.pipe(
@@ -256,82 +252,64 @@ export class CdkVirtualForOf<T> implements CollectionViewer, DoCheck, OnDestroy
256252

257253
/** Apply changes to the DOM. */
258254
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;
287278
});
288279

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;
292282
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);
296287
}
297-
298-
// We now have a full list of everything to be inserted, so go ahead and insert them.
299-
this._insertViews(insertTuples);
300288
}
301289

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();
332296
}
333297
}
334298

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+
335313
/** Update the computed properties on the `CdkVirtualForOfContext`. */
336314
private _updateComputedContextProperties(context: CdkVirtualForOfContext<any>) {
337315
context.first = context.index === 0;

src/demo-app/demo-app-module.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {HttpClientModule} from '@angular/common/http';
910
import {ApplicationRef, NgModule} from '@angular/core';
1011
import {BrowserModule} from '@angular/platform-browser';
11-
import {HttpClientModule} from '@angular/common/http';
12-
import {RouterModule} from '@angular/router';
1312
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
14-
import {ALL_ROUTES} from './demo-app/routes';
13+
import {RouterModule} from '@angular/router';
14+
import {AccessibilityDemoModule} from './a11y/a11y-module';
1515
import {EntryApp} from './demo-app/demo-app';
1616
import {DemoModule} from './demo-app/demo-module';
17-
import {AccessibilityDemoModule} from './a11y/a11y-module';
17+
import {ALL_ROUTES} from './demo-app/routes';
1818

1919

2020
@NgModule({

src/demo-app/demo-app/demo-module.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,11 @@ import {
6363
} from '../tabs/tabs-demo';
6464
import {ToolbarDemo} from '../toolbar/toolbar-demo';
6565
import {TooltipDemo} from '../tooltip/tooltip-demo';
66+
import {TreeDemoModule} from '../tree/tree-demo-module';
6667
import {TypographyDemo} from '../typography/typography-demo';
6768
import {VirtualScrollDemo} from '../virtual-scroll/virtual-scroll-demo';
6869
import {DemoApp, Home} from './demo-app';
6970
import {DEMO_APP_ROUTES} from './routes';
70-
import {TableDemoModule} from '../table/table-demo-module';
71-
import {TreeDemoModule} from '../tree/tree-demo-module';
7271

7372
@NgModule({
7473
imports: [

0 commit comments

Comments
 (0)