sass-references/angular-material/material/table/table.md

439 lines
19 KiB
Markdown

The `mat-table` provides a Material Design styled data-table that can be used to display rows of
data.
This table builds on the foundation of the CDK data-table and uses a similar interface for its
data input and template, except that its element and attribute selectors will be prefixed
with `mat-` instead of `cdk-`. For more information on the interface and a detailed look at how
the table is implemented, see the
[guide covering the CDK data-table](https://material.angular.io/guide/cdk-table).
### Getting Started
<!-- example(table-basic) -->
#### 1. Write your mat-table and provide data
Begin by adding the `<table mat-table>` component to your template and passing in data.
The simplest way to provide data to the table is by passing a data array to the table's `dataSource`
input. The table will take the array and render a row for each object in the data array.
```html
<table mat-table [dataSource]="myDataArray">
...
</table>
```
Since the table optimizes for performance, it will not automatically check for changes to the data
array. Instead, when objects are added, removed, or moved on the data array, you can trigger an
update to the table's rendered rows by calling its `renderRows()` method.
While an array is the _simplest_ way to bind data into the data source, it is also
the most limited. For more complex applications, using a `DataSource` instance
is recommended. See the section "Advanced data sources" below for more information.
#### 2. Define the column templates
Next, write your table's column templates.
Each column definition should be given a unique name and contain the content for its header and row
cells.
Here's a simple column definition with the name `'score'`. The header cell contains the text
"Score" and each row cell will render the `score` property of each row's data.
```html
<ng-container matColumnDef="score">
<th mat-header-cell *matHeaderCellDef> Score </th>
<td mat-cell *matCellDef="let user"> {{user.score}} </td>
</ng-container>
```
Note that the cell templates are not restricted to only showing simple string values, but are
flexible and allow you to provide any template.
If your column is only responsible for rendering a single string value for the header and cells,
you can instead define your column using the `mat-text-column`. The following column definition is
equivalent to the one above.
```html
<mat-text-column name="score"></mat-text-column>
```
Check out the API docs and examples of the `mat-text-column` to see how you can customize the header
text, text alignment, and cell data accessor. Note that this is not compatible with the flex-layout
table. Also, a data accessor should be provided if your data may have its properties minified
since the string name will no longer match after minification.
#### 3. Define the row templates
Finally, once you have defined your columns, you need to tell the table which columns will be
rendered in the header and data rows.
To start, create a variable in your component that contains the list of the columns you want to
render.
```ts
columnsToDisplay = ['userName', 'age'];
```
Then add `mat-header-row` and `mat-row` to the content of your `mat-table` and provide your
column list as inputs.
```html
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<tr mat-row *matRowDef="let myRowData; columns: columnsToDisplay"></tr>
```
Note that this list of columns provided to the rows can be in any order, not necessarily the order in
which you wrote the column definitions. Also, you do not necessarily have to include every column
that was defined in your template.
This means that by changing your column list provided to the rows, you can easily re-order and
include/exclude columns dynamically.
### Advanced data sources
The simplest way to provide data to your table is by passing a data array. More complex use-cases
may benefit from a more flexible approach involving an Observable stream or by encapsulating your
data source logic into a `DataSource` class.
#### Observable stream of data arrays
An alternative approach to providing data to the table is by passing an Observable stream that emits
the data array to be rendered each time it is changed. The table will listen to this stream and
automatically trigger an update to the rows each time a new data array is emitted.
#### DataSource
For most real-world applications, providing the table a `DataSource` instance will be the best way to
manage data. The `DataSource` is meant to serve as a place to encapsulate any sorting, filtering,
pagination, and data retrieval logic specific to the application.
A `DataSource` is simply a class that has at a minimum the following methods: `connect` and
`disconnect`. The `connect` method will be called by the table to provide an `Observable` that emits
the data array that should be rendered. The table will call `disconnect` when the table is destroyed,
which may be the right time to clean up any subscriptions that may have been registered in the
`connect` method.
Although Angular Material provides a ready-made table `DataSource` class, `MatTableDataSource`, you may
want to create your own custom `DataSource` class for more complex use cases. This can be done by
extending the abstract `DataSource` class with a custom `DataSource` class that then implements the
`connect` and `disconnect` methods. For use cases where the custom `DataSource` must also inherit
functionality by extending a different base class, the `DataSource` base class can be
implemented instead (`MyCustomDataSource extends SomeOtherBaseClass implements DataSource`) to
respect Typescript's restriction to only implement one base class.
### Styling Columns
Each table cell has an automatically generated class based on which column it appears in. The format for this
generated class is `mat-column-NAME`. For example, cells in a column named "symbol" can be targeted with the
selector `.mat-column-symbol`.
<!-- example(table-column-styling) -->
### Row Templates
Event handlers and property binding on the row templates will be applied to each row rendered by the table. For example,
adding a `(click)` handler to the row template will cause each individual row to call the handler when clicked.
<!-- example(table-row-binding) -->
### Features
The `MatTable` is focused on a single responsibility: efficiently render rows of data in a
performant and accessible way.
You'll notice that the table itself doesn't come out of the box with a lot of features, but expects
that the table will be included in a composition of components that fills out its features.
For example, you can add sorting and pagination to the table by using MatSort and MatPaginator and
mutating the data provided to the table according to their outputs.
To simplify the use case of having a table that can sort, paginate, and filter an array of data,
the Angular Material library comes with a `MatTableDataSource` that has already implemented
the logic of determining what rows should be rendered according to the current table state. To add
these feature to the table, check out their respective sections below.
#### Pagination
To paginate the table's data, add a `<mat-paginator>` after the table.
If you are using the `MatTableDataSource` for your table's data source, simply provide the
`MatPaginator` to your data source. It will automatically listen for page changes made by the user
and send the right paged data to the table.
Otherwise if you are implementing the logic to paginate your data, you will want to listen to the
paginator's `(page)` output and pass the right slice of data to your table.
For more information on using and configuring the `<mat-paginator>`, check out the
[mat-paginator docs](https://material.angular.io/components/paginator/overview).
The `MatPaginator` is one provided solution to paginating your table's data, but it is not the only
option. In fact, the table can work with any custom pagination UI or strategy since the `MatTable`
and its interface is not tied to any one specific implementation.
<!-- example(table-pagination) -->
#### Sorting
To add sorting behavior to the table, add the `matSort` directive to the table and add
`mat-sort-header` to each column header cell that should trigger sorting. Note that you have to import `MatSortModule` in order to initialize the `matSort` directive (see [API docs](https://material.angular.io/components/sort/api)).
```html
<!-- Name Column -->
<ng-container matColumnDef="position">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
<td mat-cell *matCellDef="let element"> {{element.position}} </td>
</ng-container>
```
If you are using the `MatTableDataSource` for your table's data source, provide the `MatSort`
directive to the data source and it will automatically listen for sorting changes and change the
order of data rendered by the table.
By default, the `MatTableDataSource` sorts with the assumption that the sorted column's name
matches the data property name that the column displays. For example, the following column
definition is named `position`, which matches the name of the property displayed in the row cell.
Note that if the data properties do not match the column names, or if a more complex data property
accessor is required, then a custom `sortingDataAccessor` function can be set to override the
default data accessor on the `MatTableDataSource`.
When updating the data soure asynchronously avoid doing so by recreating the entire `MatTableDataSource` as this could break sorting. Rather update it through the `MatTableDataSource.data` property.
If you are not using the `MatTableDataSource`, but instead implementing custom logic to sort your
data, listen to the sort's `(matSortChange)` event and re-order your data according to the sort state.
If you are providing a data array directly to the table, don't forget to call `renderRows()` on the
table, since it will not automatically check the array for changes.
<!-- example(table-sorting) -->
For more information on using and configuring the sorting behavior, check out the
[matSort docs](https://material.angular.io/components/sort/overview).
The `MatSort` is one provided solution to sorting your table's data, but it is not the only option.
In fact, the table can work with any custom sorting UI or strategy since the `MatTable` and
its interface is not tied to any one specific implementation.
#### Filtering
Angular Material does not provide a specific component to be used for filtering the `MatTable`
since there is no single common approach to adding a filter UI to table data.
A general strategy is to add an input where users can type in a filter string and listen to this
input to change what data is offered from the data source to the table.
If you are using the `MatTableDataSource`, simply provide the filter string to the
`MatTableDataSource`. The data source will reduce each row data to a serialized form and will filter
out the row if it does not contain the filter string. By default, the row data reducing function
will concatenate all the object values and convert them to lowercase.
For example, the data object `{id: 123, name: 'Mr. Smith', favoriteColor: 'blue'}` will be reduced
to `123mr. smithblue`. If your filter string was `blue` then it would be considered a match because
it is contained in the reduced string, and the row would be displayed in the table.
To override the default filtering behavior, a custom `filterPredicate` function can be set which
takes a data object and filter string and returns true if the data object is considered a match.
If you want to show a message when not data matches the filter, you can use the `*matNoDataRow`
directive.
<!--- example(table-filtering) -->
#### Selection
Right now there is no formal support for adding a selection UI to the table, but Angular Material
does offer the right components and pieces to set this up. The following steps are one solution but
it is not the only way to incorporate row selection in your table.
##### 1. Add a selection model
Get started by setting up a `SelectionModel` from `@angular/cdk/collections` that will maintain the
selection state.
```js
const initialSelection = [];
const allowMultiSelect = true;
this.selection = new SelectionModel<MyDataType>(allowMultiSelect, initialSelection);
```
##### 2. Define a selection column
Add a column definition for displaying the row checkboxes, including a main toggle checkbox for
the header. The column name should be added to the list of displayed columns provided to the
header and data row.
```html
<ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef>
<mat-checkbox (change)="$event ? toggleAllRows() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td mat-cell *matCellDef="let row">
<mat-checkbox (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>
```
##### 3. Add event handling logic
Implement the behavior in your component's logic to handle the header's main toggle and checking
if all rows are selected.
```js
/** Whether the number of selected elements matches the total number of rows. */
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected == numRows;
}
/** Selects all rows if they are not all selected; otherwise clear selection. */
toggleAllRows() {
this.isAllSelected() ?
this.selection.clear() :
this.dataSource.data.forEach(row => this.selection.select(row));
}
```
##### 4. Include overflow styling
Finally, adjust the styling for the select column so that its overflow is not hidden. This allows
the ripple effect to extend beyond the cell.
```css
.mat-column-select {
overflow: initial;
}
```
<!--- example(table-selection) -->
#### Footer row
A footer row can be added to the table by adding a footer row definition to the table and adding
footer cell templates to column definitions. The footer row will be rendered after the rendered
data rows.
```html
<ng-container matColumnDef="cost">
<th mat-header-cell *matHeaderCellDef> Cost </th>
<td mat-cell *matCellDef="let data"> {{data.cost}} </td>
<td mat-footer-cell *matFooterCellDef> {{totalCost}} </td>
</ng-container>
...
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<tr mat-row *matRowDef="let myRowData; columns: columnsToDisplay"></tr>
<tr mat-footer-row *matFooterRowDef="columnsToDisplay"></tr>
```
<!--- example(table-footer-row) -->
#### Sticky Rows and Columns
By using `position: sticky` styling, the table's rows and columns can be fixed so that they do not
leave the viewport even when scrolled. The table provides inputs that will automatically apply the
correct CSS styling so that the rows and columns become sticky.
In order to fix the header row to the top of the scrolling viewport containing the table, you can
add a `sticky` input to the `matHeaderRowDef`.
<!--- example(table-sticky-header) -->
Similarly, this can also be applied to the table's footer row. Note that if you are using the native
`<table>` and using Safari, then the footer will only stick if `sticky` is applied to all the
rendered footer rows.
<!--- example(table-sticky-footer) -->
It is also possible to fix cell columns to the start or end of the horizontally scrolling viewport.
To do this, add the `sticky` or `stickyEnd` directive to the `ng-container` column definition.
<!--- example(table-sticky-columns) -->
Note that on Safari mobile when using the flex-based table, a cell stuck in more than one direction
will struggle to stay in the correct position as you scroll. For example, if a header row is stuck
to the top and the first column is stuck, then the top-left-most cell will appear jittery as you
scroll.
Also, sticky positioning in Edge will appear shaky for special cases. For example, if the scrolling
container has a complex box shadow and has sibling elements, the stuck cells will appear jittery.
There is currently an [open issue with Edge](https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/17514118/)
to resolve this.
#### Multiple row templates
When using the `multiTemplateDataRows` directive to support multiple rows for each data object, the context of `*matRowDef` is the same except that the `index` value is replaced by `dataIndex` and `renderIndex`.
<!--- example(table-multiple-row-template) -->
### Accessibility
By default, `MatTable` applies `role="table"`, assuming the table's contains primarily static
content. You can change the role by explicitly setting `role="grid"` or `role="treegrid"` on the
table element. While changing the role will update child element roles, such as changing
`role="cell"` to `role="gridcell"`, this does _not_ apply additional keyboard input handling or
focus management to the table.
Always provide an accessible label for your tables via `aria-label` or `aria-labelledby` on the
table element.
### Tables with `display: flex`
The `MatTable` does not require that you use a native HTML table. Instead, you can use an
alternative approach that uses `display: flex` for the table's styles.
This alternative approach replaces the native table element tags with the `MatTable` directive
selectors. For example, `<table mat-table>` becomes `<mat-table>`; `<tr mat-row>` becomes
`<mat-row>`. The following shows a previous example using this alternative template:
```html
<mat-table [dataSource]="dataSource">
<!-- User name Definition -->
<ng-container matColumnDef="username">
<mat-header-cell *matHeaderCellDef> User name </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.username}} </mat-cell>
</ng-container>
<!-- Age Definition -->
<ng-container matColumnDef="age">
<mat-header-cell *matHeaderCellDef> Age </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.age}} </mat-cell>
</ng-container>
<!-- Title Definition -->
<ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef> Title </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.title}} </mat-cell>
</ng-container>
<!-- Header and Row Declarations -->
<mat-header-row *matHeaderRowDef="['username', 'age', 'title']"></mat-header-row>
<mat-row *matRowDef="let row; columns: ['username', 'age', 'title']"></mat-row>
</mat-table>
```
Note that this approach means you cannot include certain native-table features such colspan/rowspan
or have columns that resize themselves based on their content.
### Tables with `MatRipple`
By default, `MatTable` does not set up Material Design ripples for rows. A ripple effect can be
added to table rows by using the `MatRipple` directive from `@angular/material/core`. Due to
limitations in browsers, ripples cannot be applied native `th` or `tr` elements. The recommended
approach for setting up ripples is using the non-native `display: flex` variant of `MatTable`.
<!--- example(table-with-ripples) -->
More details about ripples on native table rows and their limitations can be found [in this issue](https://github.com/angular/components/issues/11883#issuecomment-634942981).