Skip to content

feat(table): support native table selectors #10594

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 3 commits into from
Mar 30, 2018
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
4 changes: 2 additions & 2 deletions src/cdk/table/cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class CdkColumnDef {

/** Header cell template container that adds the right classes and role. */
@Directive({
selector: 'cdk-header-cell',
selector: 'cdk-header-cell, th[cdk-header-cell]',
host: {
'class': 'cdk-header-cell',
'role': 'columnheader',
Expand All @@ -75,7 +75,7 @@ export class CdkHeaderCell {

/** Cell template container that adds the right classes and role. */
@Directive({
selector: 'cdk-cell',
selector: 'cdk-cell, td[cdk-cell]',
host: {
'class': 'cdk-cell',
'role': 'gridcell',
Expand Down
4 changes: 2 additions & 2 deletions src/cdk/table/row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export class CdkCellOutlet {
/** Header template container that contains the cell outlet. Adds the right class and role. */
@Component({
moduleId: module.id,
selector: 'cdk-header-row',
selector: 'cdk-header-row, tr[cdk-header-row]',
template: CDK_ROW_TEMPLATE,
host: {
'class': 'cdk-header-row',
Expand All @@ -165,7 +165,7 @@ export class CdkHeaderRow { }
/** Data row template container that contains the cell outlet. Adds the right class and role. */
@Component({
moduleId: module.id,
selector: 'cdk-row',
selector: 'cdk-row, tr[cdk-row]',
template: CDK_ROW_TEMPLATE,
host: {
'class': 'cdk-row',
Expand Down
8 changes: 4 additions & 4 deletions src/cdk/table/table-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {HeaderRowPlaceholder, RowPlaceholder, CdkTable} from './table';
import {CdkTable, HeaderRowPlaceholder, RowPlaceholder} from './table';
import {CdkCellOutlet, CdkHeaderRow, CdkHeaderRowDef, CdkRow, CdkRowDef} from './row';
import {CdkColumnDef, CdkHeaderCellDef, CdkHeaderCell, CdkCell, CdkCellDef} from './cell';
import {CdkCell, CdkCellDef, CdkColumnDef, CdkHeaderCell, CdkHeaderCellDef} from './cell';

const EXPORTED_DECLARATIONS = [
CdkTable,
Expand All @@ -30,8 +30,8 @@ const EXPORTED_DECLARATIONS = [

@NgModule({
imports: [CommonModule],
exports: [EXPORTED_DECLARATIONS],
declarations: [EXPORTED_DECLARATIONS]
exports: EXPORTED_DECLARATIONS,
declarations: EXPORTED_DECLARATIONS

})
export class CdkTableModule { }
94 changes: 67 additions & 27 deletions src/cdk/table/table.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
The `<cdk-table>` is an unopinionated, customizable data-table with a fully-templated API, dynamic
The `CdkTable` is an unopinionated, customizable data-table with a fully-templated API, dynamic
columns, and an accessible DOM structure. This component acts as the core upon which anyone can
build their own tailored data-table experience.

Expand All @@ -7,7 +7,7 @@ built. Because it enforces no opinions on these matters, developers have full co
interaction patterns associated with the table.

For a Material Design styled table, see the
[documentation for `<mat-table>`](https://material.angular.io/components/table) which builds on
[documentation for `MatTable`](https://material.angular.io/components/table) which builds on
top of the CDK data-table.

<!-- example(cdk-table-basic) -->
Expand All @@ -23,8 +23,8 @@ the column a name. Each column definition then further defines both a header-cel

```html
<ng-container cdkColumnDef="username">
<cdk-header-cell *cdkHeaderCellDef> User name </cdk-header-cell>
<cdk-cell *cdkCellDef="let row"> {{row.a}} </cdk-cell>
<th cdk-header-cell *cdkHeaderCellDef> User name </th>
<td cdk-cell *cdkCellDef="let row"> {{row.a}} </td>
</ng-container>
```

Expand All @@ -38,8 +38,8 @@ last).
The next step is to define the table's header-row (`cdkHeaderRowDef`) and data-row (`cdkRowDef`).

```html
<cdk-header-row *cdkHeaderRowDef="['username', 'age', 'title']"></cdk-header-row>
<cdk-row *cdkRowDef="let row; columns: ['username', 'age', 'title']"></cdk-row>
<tr cdk-header-row *cdkHeaderRowDef="['username', 'age', 'title']"></tr>
<tr cdk-row *cdkRowDef="let row; columns: ['username', 'age', 'title']"></tr>
```

These row templates accept the specific columns to be rendered via the name given to the
Expand All @@ -53,58 +53,58 @@ above.
##### Example: table with three columns

```html
<cdk-table [dataSource]="dataSource">
<table cdk-table [dataSource]="dataSource">
<!-- User name Definition -->
<ng-container cdkColumnDef="username">
<cdk-header-cell *cdkHeaderCellDef> User name </cdk-header-cell>
<cdk-cell *cdkCellDef="let row"> {{row.username}} </cdk-cell>
<th cdk-header-cell *cdkHeaderCellDef> User name </th>
<td cdk-cell *cdkCellDef="let row"> {{row.username}} </td>
</ng-container>

<!-- Age Definition -->
<ng-container cdkColumnDef="age">
<cdk-header-cell *cdkHeaderCellDef> Age </cdk-header-cell>
<cdk-cell *cdkCellDef="let row"> {{row.age}} </cdk-cell>
<th cdk-header-cell *cdkHeaderCellDef> Age </th>
<td cdk-cell *cdkCellDef="let row"> {{row.age}} </td>
</ng-container>

<!-- Title Definition -->
<ng-container cdkColumnDef="title">
<cdk-header-cell *cdkHeaderCellDef> Title </cdk-header-cell>
<cdk-cell *cdkCellDef="let row"> {{row.title}} </cdk-cell>
<th cdk-header-cell *cdkHeaderCellDef> Title </th>
<td cdk-cell *cdkCellDef="let row"> {{row.title}} </td>
</ng-container>

<!-- Header and Row Declarations -->
<cdk-header-row *cdkHeaderRowDef="['username', 'age', 'title']"></cdk-header-row>
<cdk-row *cdkRowDef="let row; columns: ['username', 'age', 'title']"></cdk-row>
</cdk-table>
<tr cdk-header-row *cdkHeaderRowDef="['username', 'age', 'title']"></tr>
<tr cdk-row *cdkRowDef="let row; columns: ['username', 'age', 'title']"></tr>
</table>
```

The columns given on the row determine which cells are rendered and in which order. Thus, the
columns can be set via binding to support dynamically changing the columns shown at run-time.

```html
<cdk-row *cdkRowDef="let row; columns: myDisplayedColumns"></cdk-row>
<tr cdk-row *cdkRowDef="let row; columns: myDisplayedColumns"></tr>
```

It is not required to display all the columns that are defined within the template,
nor use the same ordering. For example, to display the table with only `age`
and `username` and in that order, then the row and header definitions would be written as:

```html
<cdk-row *cdkRowDef="let row; columns: ['age', 'username']"></cdk-row>
<tr cdk-row *cdkRowDef="let row; columns: ['age', 'username']"></tr>
```

Event and property bindings can be added directly to the row element.

##### Example: table with event and class binding
```html
<cdk-header-row *cdkHeaderRowDef="['age', 'username']"
(click)="handleHeaderRowClick(row)">
</cdk-header-row>

<cdk-row *cdkRowDef="let row; columns: ['age', 'username']"
[class.can-vote]="row.age >= 18"
(click)="handleRowClick(row)">
</cdk-row>
<tr cdk-header-row *cdkHeaderRowDef="['age', 'username']"
(click)="handleHeaderRowClick(row)">
</tr>

<tr cdk-row *cdkRowDef="let row; columns: ['age', 'username']"
[class.can-vote]="row.age >= 18"
(click)="handleRowClick(row)">
</tr>
```

##### Styling columns
Expand Down Expand Up @@ -136,5 +136,45 @@ To improve performance, a `trackBy` function can be provided to the table simila
table how to uniquely identify rows to track how the data changes with each update.

```html
<cdk-table [dataSource]="dataSource" [trackBy]="myTrackById">
<table cdk-table [dataSource]="dataSource" [trackBy]="myTrackById">
```

### Alternate HTML to using native table

The CDK table does not require that you use a native HTML table. If you want to have full control
over the style of the table, it may be easier to follow an alternative template approach that does
not use the native table element tags.

This alternative approach replaces the native table element tags with the CDK table directive
selectors. For example, `<table cdk-table>` becomes `<cdk-table>`; `<tr cdk-row`> becomes
`<cdk-row>`. The following shows a previous example using this alternative template:

```html
<cdk-table [dataSource]="dataSource">
<!-- User name Definition -->
<ng-container cdkColumnDef="username">
<cdk-header-cell *cdkHeaderCellDef> User name </cdk-header-cell>
<cdk-cell *cdkCellDef="let row"> {{row.username}} </cdk-cell>
</ng-container>

<!-- Age Definition -->
<ng-container cdkColumnDef="age">
<cdk-header-cell *cdkHeaderCellDef> Age </cdk-header-cell>
<cdk-cell *cdkCellDef="let row"> {{row.age}} </cdk-cell>
</ng-container>

<!-- Title Definition -->
<ng-container cdkColumnDef="title">
<cdk-header-cell *cdkHeaderCellDef> Title </cdk-header-cell>
<cdk-cell *cdkCellDef="let row"> {{row.title}} </cdk-cell>
</ng-container>

<!-- Header and Row Declarations -->
<cdk-header-row *cdkHeaderRowDef="['username', 'age', 'title']"></cdk-header-row>
<cdk-row *cdkRowDef="let row; columns: ['username', 'age', 'title']"></cdk-row>
</cdk-table>
```

For an example of how to render the structure as a table, see the
[documentation for `<mat-table>`](https://material.angular.io/components/table) which includes
the style support for this approach.
43 changes: 43 additions & 0 deletions src/cdk/table/table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,19 @@ describe('CdkTable', () => {
});
});

it('should render correctly when using native HTML tags', () => {
const thisFixture = createComponent(NativeHtmlTableApp);
const thisTableElement = thisFixture.nativeElement.querySelector('table');
thisFixture.detectChanges();

expectTableToMatchContent(thisTableElement, [
['Column A', 'Column B', 'Column C'],
['a_1', 'b_1', 'c_1'],
['a_2', 'b_2', 'c_2'],
['a_3', 'b_3', 'c_3'],
]);
});

it('should render cells even if row data is falsy', () => {
const booleanRowCdkTableAppFixture = createComponent(BooleanRowCdkTableApp);
booleanRowCdkTableAppFixture.detectChanges();
Expand Down Expand Up @@ -1349,6 +1362,36 @@ class OuterTableApp {
firstRow = i => i === 0;
}

@Component({
template: `
<table cdk-table [dataSource]="dataSource">
<ng-container cdkColumnDef="column_a">
<th cdk-header-cell *cdkHeaderCellDef> Column A</th>
<td cdk-cell *cdkCellDef="let row"> {{row.a}}</td>
</ng-container>

<ng-container cdkColumnDef="column_b">
<th cdk-header-cell *cdkHeaderCellDef> Column B</th>
<td cdk-cell *cdkCellDef="let row"> {{row.b}}</td>
</ng-container>

<ng-container cdkColumnDef="column_c">
<th cdk-header-cell *cdkHeaderCellDef> Column C</th>
<td cdk-cell *cdkCellDef="let row"> {{row.c}}</td>
</ng-container>

<tr cdk-header-row *cdkHeaderRowDef="columnsToRender"></tr>
<tr cdk-row *cdkRowDef="let row; columns: columnsToRender" class="customRowClass"></tr>
</table>
`
})
class NativeHtmlTableApp {
dataSource: FakeDataSource | undefined = new FakeDataSource();
columnsToRender = ['column_a', 'column_b', 'column_c'];

@ViewChild(CdkTable) table: CdkTable<TestData>;
}

function getElements(element: Element, query: string): Element[] {
return [].slice.call(element.querySelectorAll(query));
}
Expand Down
26 changes: 21 additions & 5 deletions src/cdk/table/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import {
*/
@Directive({selector: '[rowPlaceholder]'})
export class RowPlaceholder {
constructor(public viewContainer: ViewContainerRef) { }
constructor(public viewContainer: ViewContainerRef, public elementRef: ElementRef) { }
}

/**
Expand All @@ -58,7 +58,7 @@ export class RowPlaceholder {
*/
@Directive({selector: '[headerRowPlaceholder]'})
export class HeaderRowPlaceholder {
constructor(public viewContainer: ViewContainerRef) { }
constructor(public viewContainer: ViewContainerRef, public elementRef: ElementRef) { }
}

/**
Expand All @@ -83,7 +83,7 @@ abstract class RowViewRef<T> extends EmbeddedViewRef<CdkCellOutletRowContext<T>>
*/
@Component({
moduleId: module.id,
selector: 'cdk-table',
selector: 'cdk-table, table[cdk-table]',
exportAs: 'cdkTable',
template: CDK_TABLE_TEMPLATE,
host: {
Expand Down Expand Up @@ -212,14 +212,18 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke

constructor(private readonly _differs: IterableDiffers,
private readonly _changeDetectorRef: ChangeDetectorRef,
elementRef: ElementRef,
private readonly _elementRef: ElementRef,
@Attribute('role') role: string) {
if (!role) {
elementRef.nativeElement.setAttribute('role', 'grid');
this._elementRef.nativeElement.setAttribute('role', 'grid');
}
}

ngOnInit() {
if (this._elementRef.nativeElement.nodeName === 'TABLE') {
this._applyNativeTableSections();
}

// TODO(andrewseguin): Setup a listener for scrolling, emit the calculated view to viewChange
this._dataDiffer = this._differs.find([]).create(this._trackByFn);

Expand Down Expand Up @@ -558,4 +562,16 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
return column.cell;
});
}

/** Adds native table sections (e.g. tbody) and moves the row placeholders into them. */
private _applyNativeTableSections() {
const thead = document.createElement('thead');
const tbody = document.createElement('tbody');

this._elementRef.nativeElement.appendChild(thead);
this._elementRef.nativeElement.appendChild(tbody);

thead.appendChild(this._headerRowPlaceholder.elementRef.nativeElement);
tbody.appendChild(this._rowPlaceholder.elementRef.nativeElement);
}
}
Loading