Skip to content

Commit e3bc7f1

Browse files
committed
fix(material/table): set class and role on no data row
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 c7e0f99 commit e3bc7f1

File tree

9 files changed

+66
-29
lines changed

9 files changed

+66
-29
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
@@ -296,21 +296,23 @@ describe('CdkTable', () => {
296296
});
297297

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

301301
const originalData = dataSource.data;
302302
dataSource.data = [];
303303
fixture.detectChanges();
304304

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

307309
dataSource.data = originalData;
308310
fixture.detectChanges();
309311

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

313-
expect(tableElement.textContent!.trim()).not.toContain('No data');
315+
expect(tableElement.querySelector('.cdk-no-data-row')).toBeFalsy();
314316
});
315317

316318
it('should show the no data row if there is no data on init', () => {
@@ -319,7 +321,7 @@ describe('CdkTable', () => {
319321
fixture.componentInstance.dataSource.data = [];
320322
fixture.detectChanges();
321323
tableElement = fixture.nativeElement.querySelector('.cdk-table');
322-
expect(tableElement.textContent!.trim()).toContain('No data');
324+
expect(tableElement.querySelector('.cdk-no-data-row')).toBeTruthy();
323325
expect(component.contentChangedCount).toBe(1);
324326
});
325327
});
@@ -562,17 +564,19 @@ describe('CdkTable', () => {
562564
const dataSource = thisFixture.componentInstance.dataSource!;
563565
const originalData = dataSource.data;
564566

565-
expect(tbody.textContent!.trim()).not.toContain('No data');
567+
expect(tbody.querySelector('.cdk-no-data-row')).toBeFalsy();
566568

567569
dataSource.data = [];
568570
thisFixture.detectChanges();
569571

570-
expect(tbody.textContent!.trim()).toContain('No data');
572+
const noDataRow: HTMLElement = tbody.querySelector('.cdk-no-data-row');
573+
expect(noDataRow).toBeTruthy();
574+
expect(noDataRow.getAttribute('role')).toBe('row');
571575

572576
dataSource.data = originalData;
573577
thisFixture.detectChanges();
574578

575-
expect(tbody.textContent!.trim()).not.toContain('No data');
579+
expect(tbody.querySelector('.cdk-no-data-row')).toBeFalsy();
576580
});
577581

578582
it('should apply correct roles for native table elements', () => {
@@ -741,7 +745,7 @@ describe('CdkTable', () => {
741745
fixture.componentInstance.dataSource.data = [];
742746
fixture.detectChanges();
743747

744-
expect(tableElement.textContent).toContain('No data');
748+
expect(tableElement.querySelector('.cdk-no-data-row')).toBeTruthy();
745749
});
746750

747751
describe('using when predicate', () => {
@@ -2677,7 +2681,7 @@ class RowContextCdkTableApp {
26772681
</ng-container>
26782682
26792683
<cdk-row *cdkRowDef="let row; columns: columns"></cdk-row>
2680-
<ng-template cdkNoDataRow>No data</ng-template>
2684+
<div *cdkNoDataRow>No data</div>
26812685
</cdk-table>
26822686
`
26832687
})

src/cdk/table/table.ts

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

1242-
if (noDataRow) {
1243-
const shouldShow = this._rowOutlet.viewContainer.length === 0;
1242+
if (!noDataRow) {
1243+
return;
1244+
}
1245+
1246+
const shouldShow = this._rowOutlet.viewContainer.length === 0;
1247+
1248+
if (shouldShow === this._isShowingNoDataRow) {
1249+
return;
1250+
}
1251+
1252+
const container = this._noDataRowOutlet.viewContainer;
1253+
1254+
if (shouldShow) {
1255+
const view = container.createEmbeddedView(noDataRow.templateRef);
1256+
const rootNode: HTMLElement | undefined = view.rootNodes[0];
12441257

1245-
if (shouldShow !== this._isShowingNoDataRow) {
1246-
const container = this._noDataRowOutlet.viewContainer;
1247-
shouldShow ? container.createEmbeddedView(noDataRow.templateRef) : container.clear();
1248-
this._isShowingNoDataRow = shouldShow;
1258+
// Only add the attributes if we have a single root node since it's hard
1259+
// to figure out which one to add it to when there are multiple.
1260+
if (view.rootNodes.length === 1 && rootNode?.nodeType === this._document.ELEMENT_NODE) {
1261+
rootNode.setAttribute('role', 'row');
1262+
rootNode.classList.add(noDataRow._contentClassName);
12491263
}
1264+
} else {
1265+
container.clear();
12501266
}
1267+
1268+
this._isShowingNoDataRow = shouldShow;
12511269
}
12521270

12531271
static ngAcceptInputType_multiTemplateDataRows: BooleanInput;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,5 @@ export class MatRow extends CdkRow {
113113
providers: [{provide: CdkNoDataRow, useExisting: MatNoDataRow}],
114114
})
115115
export class MatNoDataRow extends CdkNoDataRow {
116+
override _contentClassName = 'mat-mdc-no-data-row';
116117
}

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

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

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

126126
dataSource.data = [];
127127
fixture.detectChanges();
128128

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

131133
dataSource.data = initialData;
132134
fixture.detectChanges();
133135

134-
expect(tbody.textContent.trim()).not.toContain('No data');
136+
expect(tbody.querySelector('.mat-mdc-no-data-row')).toBeFalsy();
135137
});
136138

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

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

147149
fixture.componentInstance.dataSource!.data = [];
148150
fixture.detectChanges();
149151

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

152156
fixture.componentInstance.dataSource!.data = initialData;
153157
fixture.detectChanges();
154158

155-
expect(tbody.textContent.trim()).not.toContain('No data');
159+
expect(tbody.querySelector('.mat-mdc-no-data-row')).toBeFalsy();
156160
});
157161

158162
it('should show the no data row if there is no data on init', () => {
@@ -161,7 +165,7 @@ describe('MDC-based MatTable', () => {
161165
fixture.detectChanges();
162166

163167
const tbody = fixture.nativeElement.querySelector('tbody')!;
164-
expect(tbody.textContent.trim()).toContain('No data');
168+
expect(tbody.querySelector('.mat-mdc-no-data-row')).toBeTruthy();
165169
});
166170

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

src/material/table/row.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,5 @@ export class MatRow extends CdkRow {
113113
providers: [{provide: CdkNoDataRow, useExisting: MatNoDataRow}],
114114
})
115115
export class MatNoDataRow extends CdkNoDataRow {
116+
override _contentClassName = 'mat-no-data-row';
116117
}

src/material/table/table.spec.ts

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

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

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

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

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

104-
expect(table.textContent.trim()).not.toContain('No data');
106+
expect(table.querySelector('.mat-no-data-row')).toBeFalsy();
105107
});
106108

107109
it('should show the no data row if there is no data on init', () => {
@@ -110,7 +112,7 @@ describe('MatTable', () => {
110112
fixture.detectChanges();
111113

112114
const table = fixture.nativeElement.querySelector('.mat-table')!;
113-
expect(table.textContent.trim()).toContain('No data');
115+
expect(table.querySelector('.mat-no-data-row')).toBeTruthy();
114116
});
115117

116118
});
@@ -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)