Skip to content

Commit

Permalink
fix(material/table): set class and role on no data row (#23749)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
crisbeto authored Oct 27, 2021
1 parent 7ec0139 commit 1b6c935
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 31 deletions.
1 change: 1 addition & 0 deletions src/cdk/table/row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,5 +313,6 @@ export class CdkRow {}
selector: 'ng-template[cdkNoDataRow]',
})
export class CdkNoDataRow {
_contentClassName = 'cdk-no-data-row';
constructor(public templateRef: TemplateRef<any>) {}
}
22 changes: 13 additions & 9 deletions src/cdk/table/table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,21 +298,23 @@ describe('CdkTable', () => {
});

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

const originalData = dataSource.data;
dataSource.data = [];
fixture.detectChanges();

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

dataSource.data = originalData;
fixture.detectChanges();

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

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

it('should show the no data row if there is no data on init', () => {
Expand All @@ -321,7 +323,7 @@ describe('CdkTable', () => {
fixture.componentInstance.dataSource.data = [];
fixture.detectChanges();
tableElement = fixture.nativeElement.querySelector('.cdk-table');
expect(tableElement.textContent!.trim()).toContain('No data');
expect(tableElement.querySelector('.cdk-no-data-row')).toBeTruthy();
expect(component.contentChangedCount).toBe(1);
});
});
Expand Down Expand Up @@ -558,17 +560,19 @@ describe('CdkTable', () => {
const dataSource = thisFixture.componentInstance.dataSource!;
const originalData = dataSource.data;

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

dataSource.data = [];
thisFixture.detectChanges();

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

dataSource.data = originalData;
thisFixture.detectChanges();

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

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

expect(tableElement.textContent).toContain('No data');
expect(tableElement.querySelector('.cdk-no-data-row')).toBeTruthy();
});

describe('using when predicate', () => {
Expand Down Expand Up @@ -2766,7 +2770,7 @@ class RowContextCdkTableApp {
</ng-container>
<cdk-row *cdkRowDef="let row; columns: columns"></cdk-row>
<ng-template cdkNoDataRow>No data</ng-template>
<div *cdkNoDataRow>No data</div>
</cdk-table>
`,
})
Expand Down
30 changes: 24 additions & 6 deletions src/cdk/table/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1286,15 +1286,33 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
private _updateNoDataRow() {
const noDataRow = this._customNoDataRow || this._noDataRow;

if (noDataRow) {
const shouldShow = this._rowOutlet.viewContainer.length === 0;
if (!noDataRow) {
return;
}

const shouldShow = this._rowOutlet.viewContainer.length === 0;

if (shouldShow === this._isShowingNoDataRow) {
return;
}

const container = this._noDataRowOutlet.viewContainer;

if (shouldShow) {
const view = container.createEmbeddedView(noDataRow.templateRef);
const rootNode: HTMLElement | undefined = view.rootNodes[0];

if (shouldShow !== this._isShowingNoDataRow) {
const container = this._noDataRowOutlet.viewContainer;
shouldShow ? container.createEmbeddedView(noDataRow.templateRef) : container.clear();
this._isShowingNoDataRow = shouldShow;
// Only add the attributes if we have a single root node since it's hard
// to figure out which one to add it to when there are multiple.
if (view.rootNodes.length === 1 && rootNode?.nodeType === this._document.ELEMENT_NODE) {
rootNode.setAttribute('role', 'row');
rootNode.classList.add(noDataRow._contentClassName);
}
} else {
container.clear();
}

this._isShowingNoDataRow = shouldShow;
}

static ngAcceptInputType_multiTemplateDataRows: BooleanInput;
Expand Down
4 changes: 3 additions & 1 deletion src/material-experimental/mdc-table/row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,6 @@ export class MatRow extends CdkRow {}
selector: 'ng-template[matNoDataRow]',
providers: [{provide: CdkNoDataRow, useExisting: MatNoDataRow}],
})
export class MatNoDataRow extends CdkNoDataRow {}
export class MatNoDataRow extends CdkNoDataRow {
override _contentClassName = 'mat-mdc-no-data-row';
}
18 changes: 11 additions & 7 deletions src/material-experimental/mdc-table/table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,17 +123,19 @@ describe('MDC-based MatTable', () => {
const dataSource = fixture.componentInstance.dataSource!;
const initialData = dataSource.data;

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

dataSource.data = [];
fixture.detectChanges();

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

dataSource.data = initialData;
fixture.detectChanges();

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

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

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

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

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

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

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

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

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

it('should set the content styling class on the tbody', () => {
Expand Down
4 changes: 3 additions & 1 deletion src/material/table/row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,6 @@ export class MatRow extends CdkRow {}
selector: 'ng-template[matNoDataRow]',
providers: [{provide: CdkNoDataRow, useExisting: MatNoDataRow}],
})
export class MatNoDataRow extends CdkNoDataRow {}
export class MatNoDataRow extends CdkNoDataRow {
override _contentClassName = 'mat-no-data-row';
}
18 changes: 11 additions & 7 deletions src/material/table/table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,19 @@ describe('MatTable', () => {
const table = fixture.nativeElement.querySelector('.mat-table')!;
const initialData = fixture.componentInstance.dataSource!.data;

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

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

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

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

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

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

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

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

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

dataSource.data = [];
fixture.detectChanges();

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

dataSource.data = initialData;
fixture.detectChanges();

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

it('should render with MatTableDataSource and sort', () => {
Expand Down
2 changes: 2 additions & 0 deletions tools/public_api_guard/cdk/table.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ export class CdkHeaderRowDef extends _CdkHeaderRowDefBase implements CanStick, O
export class CdkNoDataRow {
constructor(templateRef: TemplateRef<any>);
// (undocumented)
_contentClassName: string;
// (undocumented)
templateRef: TemplateRef<any>;
// (undocumented)
static ɵdir: i0.ɵɵDirectiveDeclaration<CdkNoDataRow, "ng-template[cdkNoDataRow]", never, {}, {}, never>;
Expand Down
2 changes: 2 additions & 0 deletions tools/public_api_guard/material/table.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ export class MatHeaderRowDef extends CdkHeaderRowDef {

// @public
export class MatNoDataRow extends CdkNoDataRow {
// (undocumented)
_contentClassName: string;
// (undocumented)
static ɵdir: i0.ɵɵDirectiveDeclaration<MatNoDataRow, "ng-template[matNoDataRow]", never, {}, {}, never>;
// (undocumented)
Expand Down

0 comments on commit 1b6c935

Please sign in to comment.