将ngx-datatable替换为Angular material table

1. 前言

从 Angular 1.x 到 Angular 13 一直是用的 angular-data-table(for angularjs 1.x)和它的姊妹项目 ngx-datatable(for angular 2+), 但是最近需要将 Angular 项目从 Angular 14 升级到 Angular 16 的过程中发现了一些兼容性问题, 另外 ngx-datatable 也不支持 dark mode. 再次关注 ngx-datatable 以及网上的评论都有同感, ngx-datatable 的更新速度严重放缓了, 有些跟不上 Angular 更新的节奏了. 参考 reddit 上的一篇帖子Any better alternatives to ngx-datatable?, 也谈到了 ngx-datatable 更新放缓.

于是考虑将 ngx-datatable 组件替换掉, 这里有一些付费的项目, 在功能上可以完全替换, 比如ag-grid, Ignite UI for Angular Data Grid, Syncfusion Angular UI Components - Data Grid等等.

最后考察了 angular material table 在功能上完全服务我们的要求, sorting, pagination, server side pagination 之类的功能都有. 另外项目刚好使用的是 Material Design 设计风格, 那些付费方案的高级功能也用不上, 还要花时间学习, 另外考虑到 bundle 文件的大小, 最后坚定的选择了 Angular material table. 于是才有了这篇文章

2. 两种组件比较

以下是两个典型的 ngx-datatable 和 mat-table 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<ngx-datatable
#table
class="material"
[headerHeight]="50"
[limit]="10"
[columnMode]="'force'"
[footerHeight]="50"
[rowHeight]="'auto'"
[rows]="datasource"
[loadingIndicator]="loadingIndicator"
[selected]="selected"
(select)="onSelect($event)"
>
<ngx-datatable-column name="word" [sortable]="true">
<ng-template let-column="column" ngx-datatable-header-template>
<h3>单词</h3>
</ng-template>
<ng-template
ngx-datatable-cell-template
let-rowIndex="rowIndex"
let-value="value"
let-row="row"
>
<span [textContent]="value" (click)="wordSearch(row.word)"> </span>
</ng-template>
</ngx-datatable-column>
</ngx-datatable>
1
2
3
4
5
6
7
8
9
10
11
12

<table mat-table [dataSource]="datasource" matSort class="mat-elevation-z2 w-full">
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
<ng-container matColumnDef="WORD">
<th mat-header-cell *matHeaderCellDef mat-sort-header>单词</th>
<td mat-cell *matCellDef="let row">{{ row.WORD }}</td>
</ng-container>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 100]"></mat-paginator>
</div>

从示例我们可以看出

  • ngx-datatable 必须是一个 tag, 而 mat-table 可以是一个附着在 table 上的指令, 也可以是一个 tag
  • 两者都需要绑定 datasource, ngx-datatable 使用 rows 属性绑定 datasource, 而 mat-table 使用 datasource 属性
    • ngx-datatable [rows]=”datasource”
    • mat-table [dataSource]=”datasource”
  • ngx-datatable 提供了 loadingIndicator 属性, 而 mat-table 没有提供, 不过可以参考Add a spinner when Mat-table is loading? 实现, 不是很复杂.
  • 排序方面(sorting)
    • ngx-datatable 默认是可排序的, 如果不希望 Column 支持排序, 需要显式的将其标为[sortable]="false"
    • mat-table 默认是不可排序的, 需要在 mat-table 上添加 matSort, 在 th 上需要添加 mat-sort-header 指令
  • 分页(pagination)
    • ngx-datatable 分页相关的属性在 ngx-datatable 上
    • 而 mat-table 使用单独的 mat-paginator 标签
  • 过滤(filtering)
    • 两者都通过操纵 datasourcing 实现过滤
  • 字段定义方式
    • ngx-datatable 有专门的定义字段的标签 ngx-datatable-column, 一个标签定义一个字段然后使用 let-column="column", let-rowIndex="rowIndex" let-value="value" let-row="row"绑定数据
    • 而 mat-table 几乎重用了 html 的 tr, td, th 标签, 使用 directive 来标记表头, 表格, 例如使用 mat-header-cell 标记表头, mat-cell 标记单元格

了解了以上差异和共同点我们就可以着手迁移 ngx-datatable 到 Angular material table 组件了

3. 开始迁移

3.1. 备份或分支

在正式迁移前, 我们需要指定备份, 或者创建一个迁移分支, 这样我们可以反复比较迁移以前和迁移之后的应用在功能上的变化.

3.2. 迁移步骤

  • 将 ngx-datatable 替换为 mat-table, 精简后的 mat-table 只剩下如下内容. 迁移前的 ngx-datatable 的示例就不列举出来了, 可以参考上一节示例

    1
    2
    3
    4
    5
    6
    7
    <mat-table [dataSource]="datasource" matSort class="mat-elevation-z2 w-full">
    ...
    your row and column definition goes here
    ...

    </mat-table>
    <mat-paginator [pageSizeOptions]="[10, 25, 100]" showFirstLastButtons></mat-paginator>

    建议将 matSort 加上, 往往有那么一两个字段是需要排序的.
    如果需要加入分页, 如上加上 mat-paginator 用于替换 ngx-datatable 的分页功能

  • 去掉class="material", 因为 mat-table 默认已经是 material 样式

  • 去掉 headerHeight, footerHeight, [rowHeight]="'auto'" , [columnMode]="'force'"属性让其自适应

  • 将 rows 属性替换为[rows]="datasource" => [dataSource]="datasource"

  • [loadingIndicator]="loadingIndicator"替换为自定义实现

  • 将[limit]=”10” 所定义的功能移动到 mat-paginator 标签上<mat-paginator [pageSizeOptions]="[10, 25, 100]"></mat-paginator>

  • 添加 header
    定义 displayedColumns

    1
    displayedColumns: string[] = ["columnName1", "columnName2"]

    修改 html template

    1
    2
    <mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
    <mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
  • ngx-datatable-column 替换为 ng-container, 以下是一款 column 模板, 将 columnName, “栏位标题”替换掉

    1
    2
    3
    4
    5
    <ng-container matColumnDef="columnName">
    <mat-header-cell *matHeaderCellDef mat-sort-header>栏位标题</mat-header-cell>
    <mat-cell *matCellDef="let row"><span [textContent]="row.columnName"></span></mat-cell>
    </ng-container>

  • 在 ts 文件中定义@ViewChild(MatPaginator) paginator: MatPaginator;

  • 在 ts 文件中定义@ViewChild(MatSort) sort: MatSort;

  • 在 ts 文件中定义datasource: MatTableDataSource<any>; 将 datasource 替换为包装类型this.datasource = new MatTableDataSource(data);

  • 将 datasource 和 paginator 关联this.datasource.paginator = this.paginator;

  • 将 datasource 和 sort 关联this.datasource.sort = this.sort;

6. Angular 系列文章

最新更新以及更多 Angular 相关文章请访问 Angular 合集 | 鹏叔的技术博客

5. 参考文档

The Missing Guide to Angular Material