Skip to content

Commit 12be786

Browse files
committed
virtual-scroll: add support for user-provided content wrapper
1 parent eee1e7d commit 12be786

File tree

6 files changed

+131
-8
lines changed

6 files changed

+131
-8
lines changed

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,13 @@ export type CdkVirtualForOfContext<T> = {
4949
};
5050

5151

52-
/** Helper to extract size from a ClientRect. */
53-
function getSize(orientation: 'horizontal' | 'vertical', rect: ClientRect): number {
52+
/** Helper to extract size from a DOM Node. */
53+
function getSize(orientation: 'horizontal' | 'vertical', node: Node): number {
54+
const el = node as Element;
55+
if (!el.getBoundingClientRect) {
56+
return 0;
57+
}
58+
const rect = el.getBoundingClientRect();
5459
return orientation == 'horizontal' ? rect.width : rect.height;
5560
}
5661

@@ -193,15 +198,14 @@ export class CdkVirtualForOf<T> implements CollectionViewer, DoCheck, OnDestroy
193198
const rangeLen = range.end - range.start;
194199

195200
// Loop over all root nodes for all items in the range and sum up their size.
196-
// TODO(mmalerba): Make this work with non-element nodes.
197201
let totalSize = 0;
198202
let i = rangeLen;
199203
while (i--) {
200204
const view = this._viewContainerRef.get(i + renderedStartIndex) as
201205
EmbeddedViewRef<CdkVirtualForOfContext<T>> | null;
202206
let j = view ? view.rootNodes.length : 0;
203207
while (j--) {
204-
totalSize += getSize(orientation, (view!.rootNodes[j] as Element).getBoundingClientRect());
208+
totalSize += getSize(orientation, view!.rootNodes[j]);
205209
}
206210
}
207211

src/cdk-experimental/scrolling/virtual-scroll-viewport.scss

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,48 @@ cdk-virtual-scroll-viewport {
1616

1717
.cdk-virtual-scroll-orientation-horizontal .cdk-virtual-scroll-content-wrapper {
1818
bottom: 0;
19+
20+
& > dl,
21+
& > ol,
22+
& > table,
23+
& > ul {
24+
padding: {
25+
left: 0;
26+
right: 0
27+
};
28+
margin: {
29+
left: 0;
30+
right: 0
31+
};
32+
border: {
33+
left-width: 0;
34+
right-width: 0
35+
};
36+
outline: none;
37+
}
1938
}
2039

2140
.cdk-virtual-scroll-orientation-vertical .cdk-virtual-scroll-content-wrapper {
2241
right: 0;
42+
43+
& > dl,
44+
& > ol,
45+
& > table,
46+
& > ul {
47+
padding: {
48+
top: 0;
49+
bottom: 0
50+
};
51+
margin: {
52+
top: 0;
53+
bottom: 0
54+
};
55+
border: {
56+
top-width: 0;
57+
bottom-width: 0
58+
};
59+
outline: none;
60+
}
2361
}
2462

2563
// Spacer element that whose width or height will be adjusted to match the size of the entire data

src/cdk-experimental/scrolling/virtual-scroll-viewport.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
5757
@Input() orientation: 'horizontal' | 'vertical' = 'vertical';
5858

5959
/** The element that wraps the rendered content. */
60-
@ViewChild('contentWrapper') _contentWrapper: ElementRef;
60+
@ViewChild('contentWrapper') _contentWrapper: ElementRef<HTMLElement>;
6161

6262
/** A stream that emits whenever the rendered range changes. */
6363
renderedRangeStream: Observable<ListRange> = this._renderedRangeSubject.asObservable();
@@ -67,7 +67,10 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
6767
*/
6868
_totalContentSize = 0;
6969

70-
/** The the rendered content transform. */
70+
/**
71+
* The the rendered content transform, used to move the rendered portion of the content to the
72+
* correct place in the viewport.
73+
*/
7174
private _renderedContentTransform: string;
7275

7376
/** The currently rendered range of indices. */
@@ -103,7 +106,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
103106
/** A list of functions to run after the next change detection cycle. */
104107
private _runAfterChangeDetection: Function[] = [];
105108

106-
constructor(public elementRef: ElementRef,
109+
constructor(public elementRef: ElementRef<HTMLElement>,
107110
private _changeDetectorRef: ChangeDetectorRef,
108111
private _ngZone: NgZone,
109112
@Inject(VIRTUAL_SCROLL_STRATEGY) private _scrollStrategy: VirtualScrollStrategy) {}
@@ -327,7 +330,7 @@ export class CdkVirtualScrollViewport implements OnInit, OnDestroy {
327330
}
328331
}
329332

330-
for (let fn of this._runAfterChangeDetection) {
333+
for (const fn of this._runAfterChangeDetection) {
331334
fn();
332335
}
333336
this._runAfterChangeDetection = [];

src/cdk-experimental/scrolling/virtual-scroll.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,21 @@ content.
142142
...
143143
</cdk-virtual-scroll-viewport>
144144
```
145+
146+
### Elements with parent tag requirements
147+
Some HTML elements such as `<tr>` and `<li>` have limitations on the kinds of parent elements they
148+
can be placed inside. To enable virtual scrolling over these type of elements, place the elements in
149+
their proper parent, and then wrap the whole thing in a `cdk-virtual-scroll-viewport`. Be careful
150+
that the parent does not introduce additional space (e.g. via `margin` or `padding`) as it may
151+
interfere with the scrolling.
152+
153+
```html
154+
<cdk-virtual-scroll-viewport itemSize="50">
155+
<table>
156+
<tr *cdkVirtualFor="let row of rows">
157+
<td>{{row.first}}</td>
158+
<td>{{row.second}}</td>
159+
</tr>
160+
</table>
161+
</cdk-virtual-scroll-viewport>
162+
```

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,45 @@ <h2>trackBy state name</h2>
9494
<div class="demo-capital">{{state.capital}}</div>
9595
</div>
9696
</cdk-virtual-scroll-viewport>
97+
98+
<h2>Use with <code>&lt;ol&gt;</code></h2>
99+
100+
<cdk-virtual-scroll-viewport class="demo-viewport" autosize #viewport>
101+
<ol class="demo-ol" [start]="viewport.getRenderedRange().start + 1">
102+
<li *cdkVirtualFor="let state of statesObservable | async" class="demo-li">
103+
{{state.name}} - {{state.capital}}
104+
</li>
105+
</ol>
106+
</cdk-virtual-scroll-viewport>
107+
108+
<h2>Use with <code>&lt;ul&gt;</code></h2>
109+
110+
<cdk-virtual-scroll-viewport class="demo-viewport" autosize>
111+
<ul class="demo-ul">
112+
<li *cdkVirtualFor="let state of statesObservable | async" class="demo-li">
113+
{{state.name}} - {{state.capital}}
114+
</li>
115+
</ul>
116+
</cdk-virtual-scroll-viewport>
117+
118+
<h2>Use with <code>&lt;dl&gt;</code></h2>
119+
120+
<cdk-virtual-scroll-viewport class="demo-viewport" autosize>
121+
<dl class="demo-dl">
122+
<ng-container *cdkVirtualFor="let state of statesObservable | async">
123+
<dt class="demo-dt">{{state.name}}</dt>
124+
<dd class="demo-dd">{{state.capital}}</dd>
125+
</ng-container>
126+
</dl>
127+
</cdk-virtual-scroll-viewport>
128+
129+
<h2>Use with <code>&lt;table&gt;</code></h2>
130+
131+
<cdk-virtual-scroll-viewport class="demo-viewport" autosize>
132+
<table class="demo-ol">
133+
<tr *cdkVirtualFor="let state of statesObservable | async" class="demo-tr">
134+
<td class="demo-td">{{state.name}}</td>
135+
<td class="demo-td">{{state.capital}}</td>
136+
</tr>
137+
</table>
138+
</cdk-virtual-scroll-viewport>

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,21 @@
3737
.demo-capital {
3838
font-size: 14px;
3939
}
40+
41+
.demo-dt {
42+
height: 30px;
43+
font-weight: bold;
44+
}
45+
46+
.demo-dd {
47+
height: 30px;
48+
}
49+
50+
.demo-li,
51+
.demo-td {
52+
height: 50px;
53+
}
54+
55+
.demo-td {
56+
border: 1px solid gray;
57+
}

0 commit comments

Comments
 (0)