Skip to content

Commit 0e4a580

Browse files
authored
virtual-scroll: fix updating when data changes and add trackBy demos (#11085)
1 parent 285fe6c commit 0e4a580

File tree

4 files changed

+135
-6
lines changed

4 files changed

+135
-6
lines changed

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,15 @@ export class CdkVirtualForOf<T> implements CollectionViewer, DoCheck, OnDestroy
147147
private _differs: IterableDiffers,
148148
/** The virtual scrolling viewport that these items are being rendered in. */
149149
@SkipSelf() private _viewport: CdkVirtualScrollViewport) {
150-
this.dataStream.subscribe(data => this._data = data);
151-
this._viewport.renderedRangeStream.subscribe(range => this._onRenderedRangeChange(range));
150+
this.dataStream.subscribe(data => {
151+
this._data = data;
152+
this._onRenderedDataChange();
153+
});
154+
this._viewport.renderedRangeStream.subscribe(range => {
155+
this._renderedRange = range;
156+
this.viewChange.next(this._renderedRange);
157+
this._onRenderedDataChange();
158+
});
152159
this._viewport.attach(this);
153160
}
154161

@@ -213,9 +220,10 @@ export class CdkVirtualForOf<T> implements CollectionViewer, DoCheck, OnDestroy
213220
}
214221

215222
/** React to scroll state changes in the viewport. */
216-
private _onRenderedRangeChange(renderedRange: ListRange) {
217-
this._renderedRange = renderedRange;
218-
this.viewChange.next(this._renderedRange);
223+
private _onRenderedDataChange() {
224+
if (!this._renderedRange) {
225+
return;
226+
}
219227
this._renderedItems = this._data.slice(this._renderedRange.start, this._renderedRange.end);
220228
if (!this._differ) {
221229
this._differ = this._differs.find(this._renderedItems).create(this.cdkVirtualForTrackBy);

src/demo-app/virtual-scroll/virtual-scroll-demo.html

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,44 @@ <h2>Fixed size</h2>
5252
<h2>Observable data</h2>
5353

5454
<cdk-virtual-scroll-viewport class="demo-viewport" [itemSize]="50">
55-
<div *cdkVirtualFor="let size of observableData; let i = index" class="demo-item"
55+
<div *cdkVirtualFor="let size of observableData | async; let i = index" class="demo-item"
5656
[style.height.px]="size">
5757
Item #{{i}} - ({{size}}px)
5858
</div>
5959
</cdk-virtual-scroll-viewport>
60+
61+
<h2>No trackBy</h2>
62+
63+
<button (click)="sortBy('name')">Sort by state name</button>
64+
<button (click)="sortBy('capital')">Sort by state capital</button>
65+
<cdk-virtual-scroll-viewport class="demo-viewport" [itemSize]="60">
66+
<div *cdkVirtualFor="let state of statesObservable | async"
67+
class="demo-state-item">
68+
<div class="demo-state">{{state.name}}</div>
69+
<div class="demo-capital">{{state.capital}}</div>
70+
</div>
71+
</cdk-virtual-scroll-viewport>
72+
73+
<h2>trackBy index</h2>
74+
75+
<button (click)="sortBy('name')">Sort by state name</button>
76+
<button (click)="sortBy('capital')">Sort by state capital</button>
77+
<cdk-virtual-scroll-viewport class="demo-viewport" [itemSize]="60">
78+
<div *cdkVirtualFor="let state of statesObservable | async; trackBy: indexTrackFn"
79+
class="demo-state-item">
80+
<div class="demo-state">{{state.name}}</div>
81+
<div class="demo-capital">{{state.capital}}</div>
82+
</div>
83+
</cdk-virtual-scroll-viewport>
84+
85+
<h2>trackBy state name</h2>
86+
87+
<button (click)="sortBy('name')">Sort by state name</button>
88+
<button (click)="sortBy('capital')">Sort by state capital</button>
89+
<cdk-virtual-scroll-viewport class="demo-viewport" [itemSize]="60">
90+
<div *cdkVirtualFor="let state of statesObservable | async; trackBy: nameTrackFn"
91+
class="demo-state-item">
92+
<div class="demo-state">{{state.name}}</div>
93+
<div class="demo-capital">{{state.capital}}</div>
94+
</div>
95+
</cdk-virtual-scroll-viewport>

src/demo-app/virtual-scroll/virtual-scroll-demo.scss

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,19 @@
2424
writing-mode: vertical-lr;
2525
}
2626
}
27+
28+
.demo-state-item {
29+
height: 60px;
30+
display: flex;
31+
flex-direction: column;
32+
justify-content: center;
33+
}
34+
35+
.demo-state {
36+
font-size: 20px;
37+
font-weight: 500;
38+
}
39+
40+
.demo-capital {
41+
font-size: 14px;
42+
}

src/demo-app/virtual-scroll/virtual-scroll-demo.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
import {Component, ViewEncapsulation} from '@angular/core';
1010
import {BehaviorSubject} from 'rxjs/index';
1111

12+
13+
type State = {
14+
name: string,
15+
capital: string
16+
};
17+
18+
1219
@Component({
1320
moduleId: module.id,
1421
selector: 'virtual-scroll-demo',
@@ -23,6 +30,61 @@ export class VirtualScrollDemo {
2330
.map((_, i) => (1 + Math.floor((10000 - i) / 1000)) * 20);
2431
randomData = Array(10000).fill(0).map(() => Math.round(Math.random() * 100));
2532
observableData = new BehaviorSubject<number[]>([]);
33+
states = [
34+
{name: 'Alabama', capital: 'Montgomery'},
35+
{name: 'Alaska', capital: 'Juneau'},
36+
{name: 'Arizona', capital: 'Phoenix'},
37+
{name: 'Arkansas', capital: 'Little Rock'},
38+
{name: 'California', capital: 'Sacramento'},
39+
{name: 'Colorado', capital: 'Denver'},
40+
{name: 'Connecticut', capital: 'Hartford'},
41+
{name: 'Delaware', capital: 'Dover'},
42+
{name: 'Florida', capital: 'Tallahassee'},
43+
{name: 'Georgia', capital: 'Atlanta'},
44+
{name: 'Hawaii', capital: 'Honolulu'},
45+
{name: 'Idaho', capital: 'Boise'},
46+
{name: 'Illinois', capital: 'Springfield'},
47+
{name: 'Indiana', capital: 'Indianapolis'},
48+
{name: 'Iowa', capital: 'Des Moines'},
49+
{name: 'Kansas', capital: 'Topeka'},
50+
{name: 'Kentucky', capital: 'Frankfort'},
51+
{name: 'Louisiana', capital: 'Baton Rouge'},
52+
{name: 'Maine', capital: 'Augusta'},
53+
{name: 'Maryland', capital: 'Annapolis'},
54+
{name: 'Massachusetts', capital: 'Boston'},
55+
{name: 'Michigan', capital: 'Lansing'},
56+
{name: 'Minnesota', capital: 'St. Paul'},
57+
{name: 'Mississippi', capital: 'Jackson'},
58+
{name: 'Missouri', capital: 'Jefferson City'},
59+
{name: 'Montana', capital: 'Helena'},
60+
{name: 'Nebraska', capital: 'Lincoln'},
61+
{name: 'Nevada', capital: 'Carson City'},
62+
{name: 'New Hampshire', capital: 'Concord'},
63+
{name: 'New Jersey', capital: 'Trenton'},
64+
{name: 'New Mexico', capital: 'Santa Fe'},
65+
{name: 'New York', capital: 'Albany'},
66+
{name: 'North Carolina', capital: 'Raleigh'},
67+
{name: 'North Dakota', capital: 'Bismarck'},
68+
{name: 'Ohio', capital: 'Columbus'},
69+
{name: 'Oklahoma', capital: 'Oklahoma City'},
70+
{name: 'Oregon', capital: 'Salem'},
71+
{name: 'Pennsylvania', capital: 'Harrisburg'},
72+
{name: 'Rhode Island', capital: 'Providence'},
73+
{name: 'South Carolina', capital: 'Columbia'},
74+
{name: 'South Dakota', capital: 'Pierre'},
75+
{name: 'Tennessee', capital: 'Nashville'},
76+
{name: 'Texas', capital: 'Austin'},
77+
{name: 'Utah', capital: 'Salt Lake City'},
78+
{name: 'Vermont', capital: 'Montpelier'},
79+
{name: 'Virginia', capital: 'Richmond'},
80+
{name: 'Washington', capital: 'Olympia'},
81+
{name: 'West Virginia', capital: 'Charleston'},
82+
{name: 'Wisconsin', capital: 'Madison'},
83+
{name: 'Wyoming', capital: 'Cheyenne'},
84+
];
85+
statesObservable = new BehaviorSubject(this.states);
86+
indexTrackFn = (index: number) => index;
87+
nameTrackFn = (_: number, item: State) => item.name;
2688

2789
constructor() {
2890
this.emitData();
@@ -35,4 +97,11 @@ export class VirtualScrollDemo {
3597
setTimeout(() => this.emitData(), 1000);
3698
}
3799
}
100+
101+
sortBy(prop: 'name' | 'capital') {
102+
this.statesObservable.next(this.states.map(s => ({...s})).sort((a, b) => {
103+
const aProp = a[prop], bProp = b[prop];
104+
return aProp < bProp ? -1 : (aProp > bProp ? 1 : 0);
105+
}));
106+
}
38107
}

0 commit comments

Comments
 (0)