Skip to content

fix(material/table): set class and role on no data row #23749

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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