Skip to content

Commit 2255b66

Browse files
authored
fix(material-experimental/mdc-table): error when flexbox-based table is initialized (#21428)
The MDC-based table tries to add a class to its internal `tbody` element and it assumes that it's always going to find it. The assumption used to be correct since we only supported native `table` elements for the MDC implementation, however as of #20994 we also support flexbox-based ones which throw an error, because they don't have a `tbody`. These changes add a check to prevent the error and include a couple of sanity tests to catch issues like this in the future. I've also reworked the tests so that they use the MDC-based `MatPaginator` and `MatTableDataSource`, instead of the base ones.
1 parent d21a78d commit 2255b66

File tree

5 files changed

+76
-10
lines changed

5 files changed

+76
-10
lines changed

src/cdk/table/table.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
325325
private _cachedRenderRowsMap = new Map<T, WeakMap<CdkRowDef<T>, RenderRow<T>[]>>();
326326

327327
/** Whether the table is applied to a native `<table>`. */
328-
private _isNativeHtmlTable: boolean;
328+
protected _isNativeHtmlTable: boolean;
329329

330330
/**
331331
* Utility class that is responsible for applying the appropriate sticky positioning styles to

src/material-experimental/mdc-table/BUILD.bazel

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,23 @@ ng_test_library(
6565
deps = [
6666
":mdc-table",
6767
"//src/cdk/table",
68-
"//src/material/paginator",
68+
"//src/material-experimental/mdc-paginator",
6969
"//src/material/sort",
70-
"//src/material/table",
7170
"@npm//@angular/platform-browser",
7271
"@npm//rxjs",
7372
],
7473
)
7574

7675
ng_web_test_suite(
7776
name = "unit_tests",
78-
static_files = ["@npm//:node_modules/@material/data-table/dist/mdc.dataTable.js"],
77+
static_files = [
78+
"@npm//:node_modules/@material/textfield/dist/mdc.textfield.js",
79+
"@npm//:node_modules/@material/line-ripple/dist/mdc.lineRipple.js",
80+
"@npm//:node_modules/@material/notched-outline/dist/mdc.notchedOutline.js",
81+
"@npm//:node_modules/@material/ripple/dist/mdc.ripple.js",
82+
"@npm//:node_modules/@material/dom/dist/mdc.dom.js",
83+
"@npm//:node_modules/@material/data-table/dist/mdc.dataTable.js",
84+
],
7985
deps = [
8086
":table_tests_lib",
8187
"//src/material-experimental:mdc_require_config.js",

src/material-experimental/mdc-table/table.spec.ts

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ import {
77
TestBed,
88
tick
99
} from '@angular/core/testing';
10-
import {MatTable, MatTableModule} from './index';
10+
import {MatTable, MatTableDataSource, MatTableModule} from './index';
1111
import {DataSource} from '@angular/cdk/table';
1212
import {BehaviorSubject, Observable} from 'rxjs';
1313
import {MatSort, MatSortHeader, MatSortModule} from '@angular/material/sort';
14-
import {MatPaginator, MatPaginatorModule} from '@angular/material/paginator';
15-
import {MatTableDataSource} from '@angular/material/table';
14+
import {MatPaginator, MatPaginatorModule} from '@angular/material-experimental/mdc-paginator';
1615
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
1716

1817
describe('MDC-based MatTable', () => {
@@ -29,6 +28,7 @@ describe('MDC-based MatTable', () => {
2928
StickyTableApp,
3029
TableWithNgContainerRow,
3130
NestedTableApp,
31+
MatFlexTableApp,
3232
],
3333
}).compileComponents();
3434
}));
@@ -164,6 +164,14 @@ describe('MDC-based MatTable', () => {
164164
expect(tbody.textContent.trim()).toContain('No data');
165165
});
166166

167+
it('should set the content styling class on the tbody', () => {
168+
let fixture = TestBed.createComponent(NativeHtmlTableApp);
169+
fixture.detectChanges();
170+
171+
const tbodyElement = fixture.nativeElement.querySelector('tbody');
172+
expect(tbodyElement.classList).toContain('mdc-data-table__content');
173+
});
174+
167175
});
168176

169177
it('should render with MatTableDataSource and sort', () => {
@@ -213,6 +221,13 @@ describe('MDC-based MatTable', () => {
213221
}).not.toThrow();
214222
}));
215223

224+
it('should be able to render a flexbox-based table', () => {
225+
expect(() => {
226+
const fixture = TestBed.createComponent(MatFlexTableApp);
227+
fixture.detectChanges();
228+
}).not.toThrow();
229+
});
230+
216231
describe('with MatTableDataSource and sort/pagination/filter', () => {
217232
let tableElement: HTMLElement;
218233
let fixture: ComponentFixture<ArrayDataSourceMatTableApp>;
@@ -961,6 +976,45 @@ class TableWithNgContainerRow {
961976
}
962977

963978

979+
@Component({
980+
template: `
981+
<mat-table [dataSource]="dataSource">
982+
<ng-container matColumnDef="column_a">
983+
<mat-header-cell *matHeaderCellDef> Column A</mat-header-cell>
984+
<mat-cell *matCellDef="let row"> {{row.a}}</mat-cell>
985+
<mat-footer-cell *matFooterCellDef> Footer A</mat-footer-cell>
986+
</ng-container>
987+
988+
<ng-container matColumnDef="column_b">
989+
<mat-header-cell *matHeaderCellDef> Column B</mat-header-cell>
990+
<mat-cell *matCellDef="let row"> {{row.b}}</mat-cell>
991+
<mat-footer-cell *matFooterCellDef> Footer B</mat-footer-cell>
992+
</ng-container>
993+
994+
<ng-container matColumnDef="column_c">
995+
<mat-header-cell *matHeaderCellDef> Column C</mat-header-cell>
996+
<mat-cell *matCellDef="let row"> {{row.c}}</mat-cell>
997+
<mat-footer-cell *matFooterCellDef> Footer C</mat-footer-cell>
998+
</ng-container>
999+
1000+
<ng-container matColumnDef="special_column">
1001+
<mat-cell *matCellDef="let row"> fourth_row </mat-cell>
1002+
</ng-container>
1003+
1004+
<mat-header-row *matHeaderRowDef="columnsToRender"></mat-header-row>
1005+
<mat-row *matRowDef="let row; columns: columnsToRender"></mat-row>
1006+
<div *matNoDataRow>No data</div>
1007+
<mat-footer-row *matFooterRowDef="columnsToRender"></mat-footer-row>
1008+
</mat-table>
1009+
`
1010+
})
1011+
class MatFlexTableApp {
1012+
dataSource: FakeDataSource | null = new FakeDataSource();
1013+
columnsToRender = ['column_a', 'column_b', 'column_c'];
1014+
@ViewChild(MatTable) table: MatTable<TestData>;
1015+
}
1016+
1017+
9641018
function getElements(element: Element, query: string): Element[] {
9651019
return [].slice.call(element.querySelectorAll(query));
9661020
}

src/material-experimental/mdc-table/table.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,15 @@ export class MatTable<T> extends CdkTable<T> implements OnInit {
4343
/** Overrides the need to add position: sticky on every sticky cell element in `CdkTable`. */
4444
protected needsPositionStickyOnElement = false;
4545

46-
// After ngOnInit, the `CdkTable` has created and inserted the table sections (thead, tbody,
47-
// tfoot). MDC requires the `mdc-data-table__content` class to be added to the body.
4846
ngOnInit() {
4947
super.ngOnInit();
50-
this._elementRef.nativeElement.querySelector('tbody').classList.add('mdc-data-table__content');
48+
49+
// After ngOnInit, the `CdkTable` has created and inserted the table sections (thead, tbody,
50+
// tfoot). MDC requires the `mdc-data-table__content` class to be added to the body. Note that
51+
// this only applies to native tables, because we don't wrap the content of flexbox-based ones.
52+
if (this._isNativeHtmlTable) {
53+
const tbody = this._elementRef.nativeElement.querySelector('tbody');
54+
tbody.classList.add('mdc-data-table__content');
55+
}
5156
}
5257
}

tools/public_api_guard/cdk/table.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export declare class CdkTable<T> implements AfterContentChecked, CollectionViewe
199199
protected readonly _elementRef: ElementRef;
200200
_footerRowOutlet: FooterRowOutlet;
201201
_headerRowOutlet: HeaderRowOutlet;
202+
protected _isNativeHtmlTable: boolean;
202203
_multiTemplateDataRows: boolean;
203204
_noDataRow: CdkNoDataRow;
204205
_noDataRowOutlet: NoDataRowOutlet;

0 commit comments

Comments
 (0)