Skip to content

Commit 1b6c935

Browse files
authored
fix(material/table): set class and role on no data row (#23749)
Automatically adds a class to the no data row so that it's easier to style. In the process of adding the class I also noticed that the no data row doesn't have the correct `role`. Fixes #23729.
1 parent 7ec0139 commit 1b6c935

File tree

9 files changed

+70
-31
lines changed

9 files changed

+70
-31
lines changed

src/cdk/table/row.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,5 +313,6 @@ export class CdkRow {}
313313
selector: 'ng-template[cdkNoDataRow]',
314314
})
315315
export class CdkNoDataRow {
316+
_contentClassName = 'cdk-no-data-row';
316317
constructor(public templateRef: TemplateRef<any>) {}
317318
}

src/cdk/table/table.spec.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -298,21 +298,23 @@ describe('CdkTable', () => {
298298
});
299299

300300
it('should be able to show a message when no data is being displayed', () => {
301-
expect(tableElement.textContent!.trim()).not.toContain('No data');
301+
expect(tableElement.querySelector('.cdk-no-data-row')).toBeFalsy();
302302

303303
const originalData = dataSource.data;
304304
dataSource.data = [];
305305
fixture.detectChanges();
306306

307-
expect(tableElement.textContent!.trim()).toContain('No data');
307+
const noDataRow = tableElement.querySelector('.cdk-no-data-row')!;
308+
expect(noDataRow).toBeTruthy();
309+
expect(noDataRow.getAttribute('role')).toBe('row');
308310

309311
dataSource.data = originalData;
310312
fixture.detectChanges();
311313

312314
// Expect it to have emitted once on init, once when empty, and again with original data.
313315
expect(component.contentChangedCount).toBe(3);
314316

315-
expect(tableElement.textContent!.trim()).not.toContain('No data');
317+
expect(tableElement.querySelector('.cdk-no-data-row')).toBeFalsy();
316318
});
317319

318320
it('should show the no data row if there is no data on init', () => {
@@ -321,7 +323,7 @@ describe('CdkTable', () => {
321323
fixture.componentInstance.dataSource.data = [];
322324
fixture.detectChanges();
323325
tableElement = fixture.nativeElement.querySelector('.cdk-table');
324-
expect(tableElement.textContent!.trim()).toContain('No data');
326+
expect(tableElement.querySelector('.cdk-no-data-row')).toBeTruthy();
325327
expect(component.contentChangedCount).toBe(1);
326328
});
327329
});
@@ -558,17 +560,19 @@ describe('CdkTable', () => {
558560
const dataSource = thisFixture.componentInstance.dataSource!;
559561
const originalData = dataSource.data;
560562

561-
expect(tbody.textContent!.trim()).not.toContain('No data');
563+
expect(tbody.querySelector('.cdk-no-data-row')).toBeFalsy();
562564

563565
dataSource.data = [];
564566
thisFixture.detectChanges();
565567

566-
expect(tbody.textContent!.trim()).toContain('No data');
568+
const noDataRow: HTMLElement = tbody.querySelector('.cdk-no-data-row');
569+
expect(noDataRow).toBeTruthy();
570+
expect(noDataRow.getAttribute('role')).toBe('row');
567571

568572
dataSource.data = originalData;
569573
thisFixture.detectChanges();
570574

571-
expect(tbody.textContent!.trim()).not.toContain('No data');
575+
expect(tbody.querySelector('.cdk-no-data-row')).toBeFalsy();
572576
});
573577

574578
it('should apply correct roles for native table elements', () => {
@@ -743,7 +747,7 @@ describe('CdkTable', () => {
743747
fixture.componentInstance.dataSource.data = [];
744748
fixture.detectChanges();
745749

746-
expect(tableElement.textContent).toContain('No data');
750+
expect(tableElement.querySelector('.cdk-no-data-row')).toBeTruthy();
747751
});
748752

749753
describe('using when predicate', () => {
@@ -2766,7 +2770,7 @@ class RowContextCdkTableApp {
27662770
</ng-container>
27672771
27682772
<cdk-row *cdkRowDef="let row; columns: columns"></cdk-row>
2769-
<ng-template cdkNoDataRow>No data</ng-template>
2773+
<div *cdkNoDataRow>No data</div>
27702774
</cdk-table>
27712775
`,
27722776
})

src/cdk/table/table.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,15 +1286,33 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
12861286
private _updateNoDataRow() {
12871287
const noDataRow = this._customNoDataRow || this._noDataRow;
12881288

1289-
if (noDataRow) {
1290-
const shouldShow = this._rowOutlet.viewContainer.length === 0;
1289+
if (!noDataRow) {
1290+
return;
1291+
}
1292+
1293+
const shouldShow = this._rowOutlet.viewContainer.length === 0;
1294+
1295+
if (shouldShow === this._isShowingNoDataRow) {
1296+
return;
1297+
}
1298+
1299+
const container = this._noDataRowOutlet.viewContainer;
1300+
1301+
if (shouldShow) {
1302+
const view = container.createEmbeddedView(noDataRow.templateRef);
1303+
const rootNode: HTMLElement | undefined = view.rootNodes[0];
12911304

1292-
if (shouldShow !== this._isShowingNoDataRow) {
1293-
const container = this._noDataRowOutlet.viewContainer;
1294-
shouldShow ? container.createEmbeddedView(noDataRow.templateRef) : container.clear();
1295-
this._isShowingNoDataRow = shouldShow;
1305+
// Only add the attributes if we have a single root node since it's hard
1306+
// to figure out which one to add it to when there are multiple.
1307+
if (view.rootNodes.length === 1 && rootNode?.nodeType === this._document.ELEMENT_NODE) {
1308+
rootNode.setAttribute('role', 'row');
1309+
rootNode.classList.add(noDataRow._contentClassName);
12961310
}
1311+
} else {
1312+
container.clear();
12971313
}
1314+
1315+
this._isShowingNoDataRow = shouldShow;
12981316
}
12991317

13001318
static ngAcceptInputType_multiTemplateDataRows: BooleanInput;

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,6 @@ export class MatRow extends CdkRow {}
108108
selector: 'ng-template[matNoDataRow]',
109109
providers: [{provide: CdkNoDataRow, useExisting: MatNoDataRow}],
110110
})
111-
export class MatNoDataRow extends CdkNoDataRow {}
111+
export class MatNoDataRow extends CdkNoDataRow {
112+
override _contentClassName = 'mat-mdc-no-data-row';
113+
}

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,17 +123,19 @@ describe('MDC-based MatTable', () => {
123123
const dataSource = fixture.componentInstance.dataSource!;
124124
const initialData = dataSource.data;
125125

126-
expect(tbody.textContent.trim()).not.toContain('No data');
126+
expect(tbody.querySelector('.mat-mdc-no-data-row')).toBeFalsy();
127127

128128
dataSource.data = [];
129129
fixture.detectChanges();
130130

131-
expect(tbody.textContent.trim()).toContain('No data');
131+
const noDataRow: HTMLElement = tbody.querySelector('.mat-mdc-no-data-row');
132+
expect(noDataRow).toBeTruthy();
133+
expect(noDataRow.getAttribute('role')).toBe('row');
132134

133135
dataSource.data = initialData;
134136
fixture.detectChanges();
135137

136-
expect(tbody.textContent.trim()).not.toContain('No data');
138+
expect(tbody.querySelector('.mat-mdc-no-data-row')).toBeFalsy();
137139
});
138140

139141
it('should be able to show a message when no data is being displayed', () => {
@@ -144,17 +146,19 @@ describe('MDC-based MatTable', () => {
144146
const tbody = fixture.nativeElement.querySelector('tbody')!;
145147
const initialData = fixture.componentInstance.dataSource!.data;
146148

147-
expect(tbody.textContent.trim()).not.toContain('No data');
149+
expect(tbody.querySelector('.mat-mdc-no-data-row')).toBeFalsy();
148150

149151
fixture.componentInstance.dataSource!.data = [];
150152
fixture.detectChanges();
151153

152-
expect(tbody.textContent.trim()).toContain('No data');
154+
const noDataRow: HTMLElement = tbody.querySelector('.mat-mdc-no-data-row');
155+
expect(noDataRow).toBeTruthy();
156+
expect(noDataRow.getAttribute('role')).toBe('row');
153157

154158
fixture.componentInstance.dataSource!.data = initialData;
155159
fixture.detectChanges();
156160

157-
expect(tbody.textContent.trim()).not.toContain('No data');
161+
expect(tbody.querySelector('.mat-mdc-no-data-row')).toBeFalsy();
158162
});
159163

160164
it('should show the no data row if there is no data on init', () => {
@@ -163,7 +167,7 @@ describe('MDC-based MatTable', () => {
163167
fixture.detectChanges();
164168

165169
const tbody = fixture.nativeElement.querySelector('tbody')!;
166-
expect(tbody.textContent.trim()).toContain('No data');
170+
expect(tbody.querySelector('.mat-mdc-no-data-row')).toBeTruthy();
167171
});
168172

169173
it('should set the content styling class on the tbody', () => {

src/material/table/row.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,6 @@ export class MatRow extends CdkRow {}
108108
selector: 'ng-template[matNoDataRow]',
109109
providers: [{provide: CdkNoDataRow, useExisting: MatNoDataRow}],
110110
})
111-
export class MatNoDataRow extends CdkNoDataRow {}
111+
export class MatNoDataRow extends CdkNoDataRow {
112+
override _contentClassName = 'mat-no-data-row';
113+
}

src/material/table/table.spec.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,19 @@ describe('MatTable', () => {
9292
const table = fixture.nativeElement.querySelector('.mat-table')!;
9393
const initialData = fixture.componentInstance.dataSource!.data;
9494

95-
expect(table.textContent.trim()).not.toContain('No data');
95+
expect(table.querySelector('.mat-no-data-row')).toBeFalsy();
9696

9797
fixture.componentInstance.dataSource!.data = [];
9898
fixture.detectChanges();
9999

100-
expect(table.textContent.trim()).toContain('No data');
100+
const noDataRow: HTMLElement = table.querySelector('.mat-no-data-row');
101+
expect(noDataRow).toBeTruthy();
102+
expect(noDataRow.getAttribute('role')).toBe('row');
101103

102104
fixture.componentInstance.dataSource!.data = initialData;
103105
fixture.detectChanges();
104106

105-
expect(table.textContent.trim()).not.toContain('No data');
107+
expect(table.querySelector('.mat-no-data-row')).toBeFalsy();
106108
});
107109

108110
it('should show the no data row if there is no data on init', () => {
@@ -111,7 +113,7 @@ describe('MatTable', () => {
111113
fixture.detectChanges();
112114

113115
const table = fixture.nativeElement.querySelector('.mat-table')!;
114-
expect(table.textContent.trim()).toContain('No data');
116+
expect(table.querySelector('.mat-no-data-row')).toBeTruthy();
115117
});
116118
});
117119

@@ -154,17 +156,19 @@ describe('MatTable', () => {
154156
const dataSource = fixture.componentInstance.dataSource!;
155157
const initialData = dataSource.data;
156158

157-
expect(tbody.textContent.trim()).not.toContain('No data');
159+
expect(tbody.querySelector('.mat-no-data-row')).toBeFalsy();
158160

159161
dataSource.data = [];
160162
fixture.detectChanges();
161163

162-
expect(tbody.textContent.trim()).toContain('No data');
164+
const noDataRow: HTMLElement = tbody.querySelector('.mat-no-data-row');
165+
expect(noDataRow).toBeTruthy();
166+
expect(noDataRow.getAttribute('role')).toBe('row');
163167

164168
dataSource.data = initialData;
165169
fixture.detectChanges();
166170

167-
expect(tbody.textContent.trim()).not.toContain('No data');
171+
expect(tbody.querySelector('.mat-no-data-row')).toBeFalsy();
168172
});
169173

170174
it('should render with MatTableDataSource and sort', () => {

tools/public_api_guard/cdk/table.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,8 @@ export class CdkHeaderRowDef extends _CdkHeaderRowDefBase implements CanStick, O
257257
export class CdkNoDataRow {
258258
constructor(templateRef: TemplateRef<any>);
259259
// (undocumented)
260+
_contentClassName: string;
261+
// (undocumented)
260262
templateRef: TemplateRef<any>;
261263
// (undocumented)
262264
static ɵdir: i0.ɵɵDirectiveDeclaration<CdkNoDataRow, "ng-template[cdkNoDataRow]", never, {}, {}, never>;

tools/public_api_guard/material/table.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ export class MatHeaderRowDef extends CdkHeaderRowDef {
124124

125125
// @public
126126
export class MatNoDataRow extends CdkNoDataRow {
127+
// (undocumented)
128+
_contentClassName: string;
127129
// (undocumented)
128130
static ɵdir: i0.ɵɵDirectiveDeclaration<MatNoDataRow, "ng-template[matNoDataRow]", never, {}, {}, never>;
129131
// (undocumented)

0 commit comments

Comments
 (0)