Skip to content

Commit b8a1c0f

Browse files
committed
refactor(cdk/table): require virtual scrolling to be enabled explicitly
Currently virtual scrolling can be enabled by wrapping the table in a `cdk-virtual-scroll-viewport`. This is great for DX, but it appears to be breaking internally because apps had their own virtual scrolling implementations. To avoid conflicts, these changes switch to requiring the user to explicitly enable virtual scrolling through the `virtualScroll` input.
1 parent d1365ff commit b8a1c0f

File tree

10 files changed

+50
-43
lines changed

10 files changed

+50
-43
lines changed

goldens/cdk/scrolling/index.api.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@ export type _Bottom = {
2626
bottom?: number;
2727
};
2828

29-
// @public
30-
export const CDK_VIRTUAL_SCROLL_VIEWPORT: InjectionToken<CdkVirtualScrollViewport>;
31-
3229
// @public
3330
export class CdkFixedSizeVirtualScroll implements OnChanges {
3431
get itemSize(): number;

goldens/cdk/table/index.api.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,9 @@ export class CdkTable<T> implements AfterContentInit, AfterContentChecked, Colle
372372
readonly viewChange: BehaviorSubject<ListRange>;
373373
// (undocumented)
374374
protected _viewRepeater: _ViewRepeater<T, RenderRow<T>, RowContext<T>>;
375+
virtualScroll: CdkVirtualScrollViewport | null;
375376
// (undocumented)
376-
static ɵcmp: i0.ɵɵComponentDeclaration<CdkTable<any>, "cdk-table, table[cdk-table]", ["cdkTable"], { "trackBy": { "alias": "trackBy"; "required": false; }; "dataSource": { "alias": "dataSource"; "required": false; }; "multiTemplateDataRows": { "alias": "multiTemplateDataRows"; "required": false; }; "fixedLayout": { "alias": "fixedLayout"; "required": false; }; "recycleRows": { "alias": "recycleRows"; "required": false; }; }, { "contentChanged": "contentChanged"; }, ["_noDataRow", "_contentColumnDefs", "_contentRowDefs", "_contentHeaderRowDefs", "_contentFooterRowDefs"], ["caption", "colgroup, col", "*"], true, never>;
377+
static ɵcmp: i0.ɵɵComponentDeclaration<CdkTable<any>, "cdk-table, table[cdk-table]", ["cdkTable"], { "trackBy": { "alias": "trackBy"; "required": false; }; "dataSource": { "alias": "dataSource"; "required": false; }; "multiTemplateDataRows": { "alias": "multiTemplateDataRows"; "required": false; }; "fixedLayout": { "alias": "fixedLayout"; "required": false; }; "recycleRows": { "alias": "recycleRows"; "required": false; }; "virtualScroll": { "alias": "virtualScroll"; "required": false; }; }, { "contentChanged": "contentChanged"; }, ["_noDataRow", "_contentColumnDefs", "_contentRowDefs", "_contentHeaderRowDefs", "_contentFooterRowDefs"], ["caption", "colgroup, col", "*"], true, never>;
377378
// (undocumented)
378379
static ɵfac: i0.ɵɵFactoryDeclaration<CdkTable<any>, never>;
379380
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {NumberInput, coerceNumberProperty} from '../coercion';
3636
import {Observable, Subject, of as observableOf, isObservable} from 'rxjs';
3737
import {pairwise, shareReplay, startWith, switchMap, takeUntil} from 'rxjs/operators';
3838
import {CdkVirtualScrollRepeater} from './virtual-scroll-repeater';
39-
import {CDK_VIRTUAL_SCROLL_VIEWPORT} from './virtual-scroll-viewport';
39+
import {CdkVirtualScrollViewport} from './virtual-scroll-viewport';
4040

4141
/** The context for an item rendered by `CdkVirtualForOf` */
4242
export type CdkVirtualForOfContext<T> = {
@@ -87,7 +87,7 @@ export class CdkVirtualForOf<T>
8787
private _template = inject<TemplateRef<CdkVirtualForOfContext<T>>>(TemplateRef);
8888
private _differs = inject(IterableDiffers);
8989
private _viewRepeater = new _RecycleViewRepeaterStrategy<T, T, CdkVirtualForOfContext<T>>();
90-
private _viewport = inject(CDK_VIRTUAL_SCROLL_VIEWPORT, {skipSelf: true});
90+
private _viewport = inject(CdkVirtualScrollViewport, {skipSelf: true});
9191

9292
/** Emits when the rendered view of the data changes. */
9393
readonly viewChange = new Subject<ListRange>();

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,6 @@ function rangesEqual(r1: ListRange, r2: ListRange): boolean {
5858
const SCROLL_SCHEDULER =
5959
typeof requestAnimationFrame !== 'undefined' ? animationFrameScheduler : asapScheduler;
6060

61-
/**
62-
* Lightweight token that can be used to inject the `CdkVirtualScrollViewport`
63-
* without introducing a hard dependency on it.
64-
*/
65-
export const CDK_VIRTUAL_SCROLL_VIEWPORT = new InjectionToken<CdkVirtualScrollViewport>(
66-
'CDK_VIRTUAL_SCROLL_VIEWPORT',
67-
);
68-
6961
/** A viewport that virtualizes its scrolling with the help of `CdkVirtualForOf`. */
7062
@Component({
7163
selector: 'cdk-virtual-scroll-viewport',
@@ -84,7 +76,6 @@ export const CDK_VIRTUAL_SCROLL_VIEWPORT = new InjectionToken<CdkVirtualScrollVi
8476
useFactory: () =>
8577
inject(VIRTUAL_SCROLLABLE, {optional: true}) || inject(CdkVirtualScrollViewport),
8678
},
87-
{provide: CDK_VIRTUAL_SCROLL_VIEWPORT, useExisting: CdkVirtualScrollViewport},
8879
],
8980
})
9081
export class CdkVirtualScrollViewport extends CdkVirtualScrollable implements OnInit, OnDestroy {

src/cdk/table/table.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ you don't need these features, you can instruct the table to cache and recycle r
171171

172172
If you're showing a large amount of data in your table, you can use virtual scrolling to ensure a
173173
smooth experience for the user. To enable virtual scrolling, you have to wrap the CDK table in a
174-
`cdk-virtual-scroll-viewport` element and add some CSS to make it scrollable.
174+
`cdk-virtual-scroll-viewport` element, pass the viewport to the table and add some CSS to make it
175+
scrollable.
175176

176177
<!-- example(cdk-table-virtual-scroll) -->
177178

src/cdk/table/table.spec.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3292,8 +3292,15 @@ class WrapNativeHtmlTableAppOnPush {
32923292

32933293
@Component({
32943294
template: `
3295-
<cdk-virtual-scroll-viewport class="scroll-container" [itemSize]="52">
3296-
<table cdk-table [dataSource]="dataSource" [fixedLayout]="isFixedLayout()">
3295+
<cdk-virtual-scroll-viewport
3296+
class="scroll-container"
3297+
[itemSize]="52"
3298+
#viewport>
3299+
<table
3300+
cdk-table
3301+
[virtualScroll]="viewport"
3302+
[dataSource]="dataSource"
3303+
[fixedLayout]="isFixedLayout()">
32973304
<ng-container cdkColumnDef="column_a">
32983305
<th cdk-header-cell *cdkHeaderCellDef>Column A</th>
32993306
<td cdk-cell *cdkCellDef="let row"> {{row.a}}</td>
@@ -3328,7 +3335,7 @@ class WrapNativeHtmlTableAppOnPush {
33283335
})
33293336
class TableWithVirtualScroll {
33303337
@ViewChild(CdkTable) table: CdkTable<TestData>;
3331-
@ViewChild(CdkVirtualScrollViewport) viewport: CdkVirtualScrollViewport;
3338+
@ViewChild('viewport') viewport: CdkVirtualScrollViewport;
33323339
dataSource = new FakeDataSource();
33333340
columnsToRender = ['column_a', 'column_b', 'column_c'];
33343341
isFixedLayout = signal(true);

src/cdk/table/table.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,7 @@ import {
2020
ListRange,
2121
} from '../collections';
2222
import {Platform} from '../platform';
23-
import {
24-
CDK_VIRTUAL_SCROLL_VIEWPORT,
25-
type CdkVirtualScrollViewport,
26-
ViewportRuler,
27-
} from '../scrolling';
23+
import {type CdkVirtualScrollViewport, ViewportRuler} from '../scrolling';
2824

2925
import {
3026
AfterContentChecked,
@@ -301,7 +297,6 @@ export class CdkTable<T>
301297
protected _viewRepeater: _ViewRepeater<T, RenderRow<T>, RowContext<T>>;
302298
private readonly _viewportRuler = inject(ViewportRuler);
303299
private _injector = inject(Injector);
304-
private _virtualScrollViewport = inject(CDK_VIRTUAL_SCROLL_VIEWPORT, {optional: true});
305300
private _positionListener =
306301
inject(STICKY_POSITIONING_LISTENER, {optional: true}) ||
307302
inject(STICKY_POSITIONING_LISTENER, {optional: true, skipSelf: true});
@@ -565,7 +560,7 @@ export class CdkTable<T>
565560
get fixedLayout(): boolean {
566561
// Require a fixed layout when virtual scrolling is enabled, otherwise
567562
// the element the header can jump around as the user is scrolling.
568-
return this._virtualScrollViewport ? true : this._fixedLayout;
563+
return this.virtualScroll ? true : this._fixedLayout;
569564
}
570565
set fixedLayout(value: boolean) {
571566
this._fixedLayout = value;
@@ -582,6 +577,9 @@ export class CdkTable<T>
582577
*/
583578
@Input({transform: booleanAttribute}) recycleRows = false;
584579

580+
/** Viewport used to enable virtual scrolling on the table. */
581+
@Input() virtualScroll: CdkVirtualScrollViewport | null = null;
582+
585583
/**
586584
* Emits when the table completes rendering a set of data rows based on the latest data from the
587585
* data source, even if the set of rows is empty.
@@ -595,7 +593,10 @@ export class CdkTable<T>
595593
*
596594
* @docs-private
597595
*/
598-
readonly viewChange: BehaviorSubject<ListRange>;
596+
readonly viewChange: BehaviorSubject<ListRange> = new BehaviorSubject<ListRange>({
597+
start: 0,
598+
end: Number.MAX_VALUE,
599+
});
599600

600601
// Outlets in the table's template where the header, data rows, and footer will be inserted.
601602
_rowOutlet: DataRowOutlet;
@@ -638,21 +639,13 @@ export class CdkTable<T>
638639

639640
this._isServer = !this._platform.isBrowser;
640641
this._isNativeHtmlTable = this._elementRef.nativeElement.nodeName === 'TABLE';
641-
this.viewChange = new BehaviorSubject<ListRange>({
642-
start: 0,
643-
end: this._virtualScrollViewport ? 0 : Number.MAX_VALUE,
644-
});
645642

646643
// Set up the trackBy function so that it uses the `RenderRow` as its identity by default. If
647644
// the user has provided a custom trackBy, return the result of that function as evaluated
648645
// with the values of the `RenderRow`'s data and index.
649646
this._dataDiffer = this._differs.find([]).create((_i: number, dataRow: RenderRow<T>) => {
650647
return this.trackBy ? this.trackBy(dataRow.dataIndex, dataRow.data) : dataRow;
651648
});
652-
653-
if (this._virtualScrollViewport) {
654-
this._setupVirtualScrolling(this._virtualScrollViewport);
655-
}
656649
}
657650

658651
ngOnInit() {
@@ -668,9 +661,14 @@ export class CdkTable<T>
668661

669662
ngAfterContentInit() {
670663
this._viewRepeater =
671-
this.recycleRows || this._virtualScrollViewport
664+
this.recycleRows || this.virtualScroll
672665
? new _RecycleViewRepeaterStrategy()
673666
: new _DisposeViewRepeaterStrategy();
667+
668+
if (this.virtualScroll) {
669+
this._setupVirtualScrolling(this.virtualScroll);
670+
}
671+
674672
this._hasInitialized = true;
675673
}
676674

@@ -1480,6 +1478,8 @@ export class CdkTable<T>
14801478
const virtualScrollScheduler =
14811479
typeof requestAnimationFrame !== 'undefined' ? animationFrameScheduler : asapScheduler;
14821480

1481+
this.viewChange.next({start: 0, end: 0});
1482+
14831483
// Forward the rendered range computed by the virtual scroll viewport to the table.
14841484
viewport.renderedRangeStream
14851485
// We need the scheduler here, because the virtual scrolling module uses an identical

src/components-examples/cdk/table/cdk-table-virtual-scroll/cdk-table-virtual-scroll-example.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
<p>Showing {{dataSource.length}} rows</p>
22

3-
<!-- Enable virtual scrolling -->
4-
<cdk-virtual-scroll-viewport class="example-container" [itemSize]="48" [maxBufferPx]="1200" [minBufferPx]="400">
5-
<table cdk-table [dataSource]="dataSource" [trackBy]="trackBy">
3+
<cdk-virtual-scroll-viewport
4+
class="example-container"
5+
[itemSize]="48"
6+
[maxBufferPx]="1200"
7+
[minBufferPx]="400"
8+
#viewport>
9+
<!-- Enable virtual scrolling -->
10+
<table cdk-table [virtualScroll]="viewport" [dataSource]="dataSource" [trackBy]="trackBy">
611
<!-- Position Column -->
712
<ng-container cdkColumnDef="position">
813
<th cdk-header-cell *cdkHeaderCellDef> No. </th>

src/components-examples/material/table/table-virtual-scroll/table-virtual-scroll-example.html

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
<p>Showing {{dataSource.length}} rows</p>
22

3-
<!-- Enable virtual scrolling -->
4-
<cdk-virtual-scroll-viewport class="example-container" [itemSize]="52" [maxBufferPx]="1200" [minBufferPx]="400">
5-
<table mat-table [dataSource]="dataSource" [trackBy]="trackBy">
3+
<cdk-virtual-scroll-viewport
4+
class="example-container"
5+
[itemSize]="52"
6+
[maxBufferPx]="1200"
7+
[minBufferPx]="400"
8+
#viewport>
9+
<!-- Enable virtual scrolling -->
10+
<table mat-table [virtualScroll]="viewport" [dataSource]="dataSource" [trackBy]="trackBy">
611
<!-- Position Column -->
712
<ng-container matColumnDef="position">
813
<th mat-header-cell *matHeaderCellDef> No. </th>

src/material/table/table.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ An alternative approach to showing a large amount of data inside a Material tabl
181181
virtual scrolling which will only render the the visible rows in the DOM as the user is scrolling.
182182

183183
To enable virtual scrolling you have to wrap the Material table in a `<cdk-virtual-scroll-viewport>`
184-
element and add CSS to make the viewport scrollable.
184+
element, pass the viewport to the table and add CSS to make the viewport scrollable.
185185

186186
<!-- example(table-virtual-scroll) -->
187187

0 commit comments

Comments
 (0)