mat-table 提供了一个支持 Material Design 样式的表格,可用来显示多行数据。

此表格基于 CDK 中的 data-table 构建,并在数据输入和模板上采用了相同的接口,只是它的元素选择器和属性选择器都使用 mat- 前缀,而不是 cdk- 前缀。 对于该接口的更多信息以及表格的具体实现方式,参见 CDK data-table 指南

先把 <table mat-table> 组件添加到模板中,并传入数据。

为表格提供数据的最简单方式就是给它的输入属性 dataSource 提供一个数组型数据。 该表格将会接收这个数组,并把数组型数据中的每一个对象渲染成一行。

<table mat-table [dataSource]="myDataArray">
  ...
</table>

为了优化表格的性能,它不会自动检查数组型数据的变更。而是要在每次在数据表中添加、删除或移动对象时,通过调用它的 renderRows 方法来更新表格的各行。

虽然数组是把数据绑定到数据源的最简单的方式,但它也受限最严重。对于更复杂的应用,建议使用 DataSource 实例。参见稍后的 "高级数据源" 部分了解更多。

接着,编写表格的列模板。

每个列定义都应该有一个唯一的名字,并且包含其表头单元格和行内单元格的内容。

下面是一个名为 'username' 的简单列定义。 表头单元格包含一个文本 "Name",每个行内单元格将会渲染出每行数据中 name 属性的值。

<ng-container matColumnDef="score">
  <th mat-header-cell *matHeaderCellDef> Score </th>
  <td mat-cell *matCellDef="let user"> {{user.score}} </td>
</ng-container>

请注意,单元格模板不仅限于显示简单的字符串值,它非常灵活,可以让你提供任何模板。

如果你的列只负责为表头和单元格渲染一个字符串值,可以改用 mat-text-column 来定义它。下面的列定义与上面的列定义是等价的。

<mat-text-column name="score"></mat-text-column>

查看 API 文档和 mat-text-column 的例子,以了解如何自定义表头文本、文本对齐方式和单元格数据访问器。请注意,这与 flex-layout 表不兼容。此外,如果数据的属性最小化过,则应提供数据访问器,因为属性名和字符串名称在最小化后将不再匹配。

最后,一旦你定义了各个列,就要告诉表格该在表头和数据行中显示哪些列。

先在组件中创建一个变量,其中包含你要渲染的列数组。

columnsToDisplay = ['userName', 'age'];

然后,把 mat-header-rowmat-row 作为内容添加到 mat-table 中,并提供你的列数组作为输入。

<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<tr mat-row *matRowDef="let myRowData; columns: columnsToDisplay"></tr>

注意,提供给各行的列数组可以使用任意顺序,不要求和定义列的顺序一致。同样的,你也不必包含模板中定义的每一列。

这意味着通过调整提供给各行的列顺序,你可以轻易调整列序和动态包含/排除某些列。

为表格提供数据的最简单方式是传入一个数组型数据。对于更复杂的用例,则可以从一些更灵活的方式中受益,比如使用 Observable 流,或把你的数据源逻辑封装进 DataSource 类中。

为表格提供数据的另一种方法是传入一个 Observable 流,每当它变化时都会发出一个要渲染的数组型数据。 表格会监听这个流,每当它发出新的数组型数据时,就会自动触发一次更新。

对于大多数真实世界中的应用,为表格提供一个 DataSource 实例都会是管理数据的最佳方式。 DataSource 旨在封装此应用特有的排序、过滤、分页和数据接收逻辑。

DataSource 是一个至少拥有两个函数的类:connectdisconnect。 表格会调用 connect 函数,以接收一个流,流中会发出要渲染的数组型数据。当表格销毁时,就会调用 disconnect,它是清理 connect 期间所做的各种订阅的最佳时机。

虽然 Angular Material 提供了现成的表格 DataSourceMatTableDataSource,但是你可能希望针对更复杂的用例创建自己的 DataSource 类。你可以用一个自定义的 DataSource 类来扩展抽象类 DataSource,然后实现其 connectdisconnect 方法。对于这些复杂用例,自定义 DataSource 必须通过扩展其他基类( MyCustomDataSource extends SomeOtherBaseClass implements DataSource )来继承其功能,以遵守 Typescript 的限制,即只能实现一个基类。

每个表格单元格都有一个基于它出现在哪一列自动生成的类。这个生成的类的格式是 mat-column-NAME 。例如,可以使用选择器 .mat-column-symbol 将名为“symbol”的列中的单元格作为目标。

行模板上的事件处理程序和属性绑定将应用于表中渲染的每一行。例如,向行模板添加 (click) 处理程序将导致每个单独的行在单击时调用处理程序。

MatTable 专注于一个职责:以高效且具有无障碍性的方式执行数据渲染。

你可能注意到了,表格本身并没有自带很多特性,而是把该表格作为一个组件联合体的一部分,来补全其它特性。

比如,你可以把 MatSortMatPaginator 添加到表格中,以提供排序和分页特性,并根据它们的输出来修改要传给表格的数据。

对于那些可以对数组型数据进行排序、分页和过滤功能的表格,为了简化其用法,Angular Material 库自带了一个 MatTableDataSource,它已经实现了根据当前表格的状态来决定要显示哪些列的逻辑。 要给表格添加这些特性,请查看其相关部分的文档。

要想对表格数据进行分页,请在表格后添加一个 <mat-paginator>

如果你正在用 MatTableDataSource 作为表格的数据源,那么只要把 MatPaginator 提供给这个数据源就可以了。 它将会自动监听用户所做的页码变更,并把正确分页之后的数据发给该表格。

而如果你自己实现了数据分页逻辑,那就要监听该分页器的 (page) 输出,并把进行了正确的切片之后的数据发给表格。

要了解使用和配置 <mat-paginator> 的更多信息,参见 mat-paginator 的文档

MatPaginator 提供了一个对表格数据进行分页的解决方案,不过它不是唯一的选项。 事实上,该表格可以和任何自定义的分页器 UI 或策略类协同工作,因为 MatTable 及其接口并没有绑死在任何特定的实现上。

要想为表格添加排序行为,请给它添加 matSort 指令并把 mat-sort-header 指令添加到每个允许触发排序功能的表头上。

<!-- 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>

如果你正在用 MatTableDataSource 作为数据源,可以把 MatSort 提供给数据源,这样它就会自动监听排序的更改,并据此修改表格中数据的排序顺序。

默认情况下,MatTableDataSource 会假设已排序列的名称和该列所显示的属性名是一致的。比如,下面的列定义名叫 position,它和要显示在单元格中的属性名是一样的。

注意,如果数据属性与列名不一致,或需要更复杂的数据属性访问器,那么可以设置一个自定义的 sortingDataAccessor 函数,以覆盖 MatTableDataSource 上默认的数据访问器。

如果你没有使用 MatTableDataSource 而是自己实现了数据排序逻辑,那么可以监听此排序器的 (matSortChange) 事件,并根据其排序状态重新排序你的数据。 如果你要直接给表格提供一个数组型数据,别忘了调用表格的 renderRows() 函数,因为它不会自动检查对数组的修改。

要了解使用和配置排序行为的更多信息,参见 matSort 的文档

MatSort 是用来排序表格数据的一个现成的解决方案,但它不是唯一的选择。 事实上,表格可以和任何一个自定义的排序 UI 或策略类协同工作,因为 MatTable 及其接口没有绑死到任何一个特定的实现。

Angular Material 没有提供用于过滤 MatTable 的具体组件,因为没有一种简单通用的方式可以为表格数据添加过滤界面。

通常的策略是添加一个输入框,用户可以在其中输入过滤字符串,并监听此输入,以修改从数据源提供给表格的数据。

如果你正在使用 MatTableDataSource 只要将过滤字符串提供给 MatTableDataSource 就可以了。 数据源将会把每一行数据进行缩减,并根据本行有没有包含该过滤字符串进行筛选。 默认情况下,行数据的缩减函数会把传给它的对象的所有值都连接起来,并转换成小写。

比如,数据 {id: 123, name: 'Mr. Smith', favoriteColor: 'blue'} 将会缩减成 123mr. smithblue。 如果你的过滤字符串是 blue,那么它就是匹配的,因为它包含在缩减后的字符串中,这一行就会显示在表格中。

要覆盖这种默认的过滤行为,可以设置一个自定义的 filterPredicate 函数,它可以接受一个数据对象和过滤器字符串,如果认为该数据对象是匹配的,就返回 true

如果你想在过滤器匹配不上数据时显示某种信息,可以使用 *matNoDataRow 指令。

目前,还没有对给表格添加选取界面提供正式的支持。不过 Angular Material 提供了一些组件和代码片段来支持它。 下面这些步骤是为表格添加列选择功能的解决方案之一(但不是唯一的)。

首先建立来自 @angular/cdk/collectionsSelectionModel,它用来维护选取状态。

const initialSelection = [];
const allowMultiSelect = true;
this.selection = new SelectionModel<MyDataType>(allowMultiSelect, initialSelection);

添加一个列定义,以显示本行的检查框,包括给标题行的主控检查框。 其列名也要添加到提供给表头和数据行的待显示列数组中。

<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>

在组件逻辑中实现一些行为,以处理表头的主控开关,并检查是否所有的行都被选中了。

/** 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));
}

最后,调整选取列的样式,让它的 overflow 不是 hidden。这样就可以让涟漪效果延伸到单元格之外。

.mat-column-select {
  overflow: initial;
}

添加一个表尾的定义并为其指定一个单元格模板,可以为表格添加一个表尾。表尾会显示在所有数据行之后。

<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>

通过使用 position: sticky 样式,可以固定住表格的某些行和列,这样它们在滚动时就不会离开视野。 表格所提供的输入属性将会自动应用正确的 CSS 样式,以便这些行和列能被钉住。

要想把表头行固定到包含此表格的视野顶部,你可以给 matHeaderRowDef 添加输入属性 sticky

同样的,我们也可以钉住表格的表尾。注意,如果你正在使用原生的 <table> 和 Safari,那么只有当所有的尾行都带有 sticky 的时候,表尾才会被钉住。

还可以把一些列的单元格固定在水平滚动视图的头部和尾部,只要把 stickystickyEnd 指令添加到 ng-container 的列定义上就可以了。

注意,在移动版的 Safari 上,如果使用 Flex 布局的表格,当滚动时,钉在多于一个方向的单元格将难以保持在正确的位置上。 比如,如果表头钉在顶部,而且第一列也钉住了,那么当滚动时,其左上角的单元格将会不断抖动。

此外,在一些特殊情况下,Edge 中的定位也会不稳定。比如,如果滚动容器具有复杂的阴影而且还有兄弟元素,那么钉住的单元格就会抖动。 这里是 Edge 上关于此问题的 Issue

当使用 multiTemplateDataRows 指令以支持让每个数据对象对应多行时,*matRowDef 中使用同一个上下文,不过其 index 值要换成 dataIndexrenderIndex

默认情况下, MatTable 会应用 role="table",这里假设表格主要包含静态内容。你可以通过在 table 元素上显式设置 role="grid"role="treegrid" 来更改其角色。虽然更改角色会更新子元素角色,例如将 role="cell" 更改为 role="gridcell" ,但这不会对表格应用额外的键盘输入处理或焦点管理逻辑。

始终通过表格元素上的 aria-labelaria-labelledby 为你的表格提供无障碍标签。

MatTable 不要求你使用原生 HTML 表格,所以你可以用另一种基于 display: flex 的方式来控制表格的样式。

这种方式把原生的 table 元素标记替换成 MatTable 指令的选择器。 比如 <table mat-table> 变成了 <mat-table><tr mat-row> 变成了 <mat-row>。下面的例子用这种方式改写了以前的例子:

<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>

注意,这种方法意味着你不能使用某些原生表格的专属特性,比如 colspan/rowspan 或一些能根据其内容自动调整自身大小的列。

默认情况下, MatTable 不会为行设置 Material Design 涟漪。可以使用来自 @angular/material/coreMatRipple 指令将涟漪效果添加到表行。由于浏览器的限制,涟漪不能应用于原生 thtr 元素。设置涟漪的推荐方法是使用 MatTable 的非原生 display: flex 变体。

有关原生表行上的涟漪及其限制的更多详细信息,请参见本错误