diff --git a/src/cdk/table/table.md b/src/cdk/table/table.md index db12c29f913f..ad7bdd942f37 100644 --- a/src/cdk/table/table.md +++ b/src/cdk/table/table.md @@ -173,6 +173,10 @@ If you're showing a large amount of data in your table, you can use virtual scro smooth experience for the user. To enable virtual scrolling, you have to wrap the CDK table in a `cdk-virtual-scroll-viewport` element and add some CSS to make it scrollable. +**Note:** tables with virtual scrolling have the following limitations: +* `fixedLayout` is always enabled, in order to prevent jumping when rows are swapped out. +* Conditional templates via the `when` input are not supported. + ### Alternate HTML to using native table diff --git a/src/cdk/table/table.spec.ts b/src/cdk/table/table.spec.ts index 842033adad87..2bc5582bab80 100644 --- a/src/cdk/table/table.spec.ts +++ b/src/cdk/table/table.spec.ts @@ -2007,11 +2007,11 @@ describe('CdkTable', () => { }); describe('virtual scrolling', () => { - let fixture: ComponentFixture; - let table: HTMLTableElement; - - beforeEach(fakeAsync(() => { - fixture = TestBed.createComponent(TableWithVirtualScroll); + function createVirtualScroll(component: Type): { + fixture: ComponentFixture; + table: HTMLTableElement; + } { + const fixture = TestBed.createComponent(component); // Init logic copied from the virtual scroll tests. fixture.detectChanges(); @@ -2021,10 +2021,17 @@ describe('CdkTable', () => { tick(16); flush(); fixture.detectChanges(); - table = fixture.nativeElement.querySelector('table'); - })); - function triggerScroll(offset: number) { + return { + fixture, + table: fixture.nativeElement.querySelector('table'), + }; + } + + function triggerScroll( + fixture: ComponentFixture<{viewport: CdkVirtualScrollViewport}>, + offset: number, + ) { const viewport = fixture.componentInstance.viewport; viewport.scrollToOffset(offset); dispatchFakeEvent(viewport.scrollable!.getElementRef().nativeElement, 'scroll'); @@ -2032,24 +2039,28 @@ describe('CdkTable', () => { } it('should not render the full data set when using virtual scrolling', fakeAsync(() => { + const {fixture, table} = createVirtualScroll(TableWithVirtualScroll); expect(fixture.componentInstance.dataSource.data.length).toBeGreaterThan(2000); expect(getRows(table).length).toBe(10); })); it('should maintain a limited amount of data as the user is scrolling', fakeAsync(() => { + const {fixture, table} = createVirtualScroll(TableWithVirtualScroll); expect(getRows(table).length).toBe(10); - triggerScroll(500); + triggerScroll(fixture, 500); expect(getRows(table).length).toBe(13); - triggerScroll(500); + triggerScroll(fixture, 500); expect(getRows(table).length).toBe(13); - triggerScroll(1000); + triggerScroll(fixture, 1000); expect(getRows(table).length).toBe(12); })); it('should update the table data as the user is scrolling', fakeAsync(() => { + const {fixture, table} = createVirtualScroll(TableWithVirtualScroll); + expectTableToMatchContent(table, [ ['Column A', 'Column B', 'Column C'], ['a_1', 'b_1', 'c_1'], @@ -2065,7 +2076,7 @@ describe('CdkTable', () => { ['Footer A', 'Footer B', 'Footer C'], ]); - triggerScroll(1000); + triggerScroll(fixture, 1000); expectTableToMatchContent(table, [ ['Column A', 'Column B', 'Column C'], @@ -2086,17 +2097,19 @@ describe('CdkTable', () => { })); it('should update the position of sticky cells as the user is scrolling', fakeAsync(() => { + const {fixture, table} = createVirtualScroll(TableWithVirtualScroll); const assertStickyOffsets = (position: number) => { getHeaderCells(table).forEach(cell => expect(cell.style.top).toBe(`${position * -1}px`)); getFooterCells(table).forEach(cell => expect(cell.style.bottom).toBe(`${position}px`)); }; assertStickyOffsets(0); - triggerScroll(1000); + triggerScroll(fixture, 1000); assertStickyOffsets(884); })); it('should force tables with virtual scrolling to have a fixed layout', fakeAsync(() => { + const {fixture, table} = createVirtualScroll(TableWithVirtualScroll); expect(fixture.componentInstance.isFixedLayout()).toBe(true); expect(table.classList).toContain('cdk-table-fixed-layout'); @@ -2105,6 +2118,14 @@ describe('CdkTable', () => { expect(table.classList).toContain('cdk-table-fixed-layout'); })); + + it('should throw if multiple row templates are used with virtual scrolling', fakeAsync(() => { + expect(() => { + createVirtualScroll(TableWithVirtualScrollAndMultipleDefinitions); + }).toThrowError( + /Conditional row definitions via the `when` input are not supported when virtual scrolling is enabled/, + ); + })); }); }); @@ -3338,6 +3359,25 @@ class TableWithVirtualScroll { } } +@Component({ + template: ` + + + + + + + + +
{{row.a}}
+
+ `, + imports: [CdkTableModule, ScrollingModule], +}) +class TableWithVirtualScrollAndMultipleDefinitions extends TableWithVirtualScroll { + predicate = () => true; +} + function getElements(element: Element, query: string): HTMLElement[] { return [].slice.call(element.querySelectorAll(query)); } diff --git a/src/cdk/table/table.ts b/src/cdk/table/table.ts index 8d1781ae5be4..51cfef98f38e 100644 --- a/src/cdk/table/table.ts +++ b/src/cdk/table/table.ts @@ -1147,12 +1147,18 @@ export class CdkTable // After all row definitions are determined, find the row definition to be considered default. const defaultRowDefs = this._rowDefs.filter(def => !def.when); - if ( - !this.multiTemplateDataRows && - defaultRowDefs.length > 1 && - (typeof ngDevMode === 'undefined' || ngDevMode) - ) { - throw getTableMultipleDefaultRowDefsError(); + + if (typeof ngDevMode === 'undefined' || ngDevMode) { + if (this._virtualScrollEnabled() && this._rowDefs.some(def => def.when)) { + throw new Error( + 'Conditional row definitions via the `when` input are not ' + + 'supported when virtual scrolling is enabled.', + ); + } + + if (!this.multiTemplateDataRows && defaultRowDefs.length > 1) { + throw getTableMultipleDefaultRowDefsError(); + } } this._defaultRowDef = defaultRowDefs[0]; } @@ -1317,7 +1323,7 @@ export class CdkTable * definition. */ _getRowDefs(data: T, dataIndex: number): CdkRowDef[] { - if (this._rowDefs.length == 1) { + if (this._rowDefs.length === 1) { return [this._rowDefs[0]]; } diff --git a/src/material/table/table.md b/src/material/table/table.md index 2f9aba0001b1..5c3b8c9c0055 100644 --- a/src/material/table/table.md +++ b/src/material/table/table.md @@ -183,6 +183,10 @@ virtual scrolling which will only render the the visible rows in the DOM as the To enable virtual scrolling you have to wrap the Material table in a `` element and add CSS to make the viewport scrollable. +**Note:** tables with virtual scrolling have the following limitations: +* `fixedLayout` is always enabled, in order to prevent jumping when rows are swapped out. +* Conditional templates via the `when` input are not supported. + #### Sorting