使用mat-paginator在Angular mat表中进行服务器端分页

1. 前言

在上一篇文章中,我们学习了如何使用 Angular 添加客户端分页。mat-tablemat-paginator

在客户端分页中,我们将从服务器一次获取所有记录并使用 mat-paginator 组件应用分页。

但是,如果我们的数据量很大,即记录数量较多,那么应用客户端分页并不是一个好主意。

这可能会导致严重的性能影响。

在这种情况下,我们将在 Angular mat-table 中实现服务器端分页。

2. 什么是 Angular 服务器端分页?

假设我们必须在 Angular mat-table 组件中显示 1000 条记录.

在客户端分页示例中, 我们将从服务器获取所有 1000 条记录并将其绑定到 mat-table 的数据源,并进一步使用 mat-paginator 组件实现分页功能。

使用 HttpClient.get()从服务器获取 1000 条记录可能会很慢。

服务器必须从数据库中读取所有记录并将其以 JSON 格式返回给客户端。JSON 数据会非常大。

可能 1000 条记录没什么大不了的,想象一下加载大约 1,00,000 条记录会怎样?

这样带来的后果是, 每次用户请求数据时, 服务器的内存, 包括 CPU 的处理压力会非常大, 网络带宽占用会非常严重. 客户端也会显得特别卡顿, 延迟会非常久.

通常情况下, 用户一次也浏览不了如此多的数据, 造成了非常大的性能浪费, 而带来的实际用户体验也不好. 于是 Server side 分页技术应运而生.

Server side 分页是怎样做的呢?

在 mat-table 的服务器端分页分页技术中, 我们只展示首页数据, 相应的页数和每一页的记录条数.

通过单击下一页或页码,它将再次去服务器端获取并呈现相应页面的数据。

假设我们的页面大小是“10”。

我们将在初始加载时仅从服务器获取前 10 条记录并将其显示在 UI 中,而不是一次性获取 1000 条记录。

如果用户点击第二页,我们将再次调用服务器来获取接下来的 10 条记录。

所以每次我们点击下一页或页码时我们都会去服务器获取相应的页面记录。这称为服务器端分页。

服务器 API 应该接受页面大小和页码参数。

这是在 Angular 中实现服务器端分页的最低要求。

来自服务器的数据应具有以下数据格式。

1
2
3
4
5
6
7
8
9
10
Server API : "/api/users?currentPage=1&pageSize=6"

// returns
{
data: [{},{}]
currentPage: 1
pageSize: 6
totalPages: 2
totalRecords: 12
}

该 data 属性包含要在 mat-table 中显示的记录.

currentPage 只是页码。

其余属性 pageSize、totalPages 和 totalRecords 比较好理解。

您的 JSON 数据不必采用上述格式,但这些属性是必要的,并且在显示表记录时会很有帮助。

它是广泛接受的服务器端分页格式。

3. 在 Angular 中实现服务器端分页的步骤

3.1. 添加 mat-paginator 到 mat-table

首先,我们将使用 向材料表添加分页 mat-paginator。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container [matColumnDef]="column" *ngFor="let column of displayedColumns">
<th mat-header-cell *matHeaderCellDef>{{ column }}</th>
<td mat-cell *matCellDef="let emp">{{ emp[column] }}</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let emprow; columns: displayedColumns"></tr>
</table>

<mat-paginator
#paginator
[pageSizeOptions]="pageSizes"
showFirstLastButtons
></mat-paginator>

在 component.ts 文件中

1
2
3
4
5
6
7

dataSource = new MatTableDataSource<Employee>();

@ViewChild('paginator') paginator: MatPaginator;

pageSizes = [3, 5, 7];

在此示例中,我们将在 mat-table 显示员工列表。

1
2
3
4
5
6
7
8
9

export interface Employee {
id: number;
first_name: string;
last_name: string;
email: string;
avatar: string;
}

3.2. 添加 length 属性 mat-paginator 上

mat-paginator 有一个属性叫 length,它表示 mat-table 中的记录总数。

我创建了一个名为的变量 totalDataRecords 并将其绑定到该 length 属性。

我创建了一个名为的变量 totalDataRecords 并将其绑定到该 length 属性。

1
2
3
4
5
6
7
8

<mat-paginator
#paginator
[length]="totalDataRecords"
[pageSizeOptions]="pageSizes"
showFirstLastButtons
></mat-paginator>

3.3. 创建支持页码和页面大小参数的 API

如上所述,我们的服务器 API 应该接受页码和页面长度参数,以实现服务器端分页。

这里, 我没有打算讲解服务器端如何实现这样的 API, 我打算利用一个第三方实现的 API, 我们将重点放在前端 mat-table 和 mat-paginator 的讲解上.

至于后端使用什么技术实现, 如何优雅地实现, 我们将有单独的文章讲解, 关注鹏叔的技术博客第一时间获得更新.

当前我将使用第三方 API https://reqres.in,它返回员工详细信息列表,并且还接受两个名为 page 和的参数 per_page。

1
https://reqres.in/api/users?page=1&per_page=5

这里 page 只是页码,per_page 代表页面大小。

3.4. 创建服务

现在我们将创建一个名为 的服务 EmployeeService,其方法为 getEmployees()。

在这个方法中,使用方法我们将调用 API。HttpClient.get()

1
2
3
4
5
6
7
8
9
10
11
12
export class EmployeeService {
constructor(private http: HttpClient) {}

public getEmployees(
pageNumber: Number,
pageSize: Number
): Observable<EmployeeTable> {
const url = `https://reqres.in/api/users?page=${pageNumber}&per_page=${pageSize}`;

return this.http.get<EmployeeTable>(url);
}
}

在上面的代码中,我使用了 EmployeeTableobject 而不是 Employee,这是因为我们的服务器 API 以以下格式返回数据。

1
2
3
4
5
6
7
export interface EmployeeTable {
data: Employee[];
page: number;
per_page: number;
total: number;
total_pages: number;
}

将此服务注入到我们的 component.ts 文件中。

1
2
3
4
5
6
7

constructor(public empService: EmployeeService) {}

getTableData$(pageNumber: Number, pageSize: Number) {
return this.empService.getEmployees(pageNumber, pageSize);
}

我在 component.ts 文件中添加了一个本地方法,该方法 getEmployees()调用 EmployeeService.

3.5. 订阅 mat-paginator 页面事件

mat-paginator 包含一个名为 的属性 page,其 EventEmitter 类型为 PageEvent。

并且会在分页器更改页面大小或页面索引(即页码)时触发。

在 ngAfterViewInit()方法中,可以订阅 this.paginator.page 事件。

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

ngAfterViewInit() {

this.dataSource.paginator = this.paginator;

this.paginator.page
.pipe(
startWith({}),
switchMap(() => {
return this.getTableData$(
this.paginator.pageIndex + 1,
this.paginator.pageSize
).pipe(catchError(() => observableOf(null)));
}),
map((empData) => {
if (empData == null) return [];
this.totalDataRecords = empData.total;
return empData.data;
})
)
.subscribe((empData) => {
this.EmpData = empData;
this.dataSource = new MatTableDataSource(this.EmpData);
});
}

以上示例使用了一系列 Rxjs 算子,例如 startWith 和 switchMap 和 map 以及分页器的 PageEvent。

在 switchMap 操作符中,我返回 getTableData$带有页码和页面大小参数的 observable。

mat-paginator 包含 pageIndex 和 pageSize 属性,表示 的页码和页面大小。

mat-table 从零开始 pageIndex,我将添加+1 到页面索引。

在 map 运算符中,我从 dataEmployeeTable 的属性 totalDataRecords 返回员工记录总条数。

最后在订阅方法中,将员工数据分配给 mat-table 的数据源。

每当 发生变化时 mat-paginator,即当我们单击上一页/下一页链接或更改页面大小时,就会调用上述页面事件,并且表数据将相应更新。

3.6. 添加进度条

由于我们在每个 mat-paginator 更改事件都会调用服务器 API ,因此最好将进度条添加到 mat-table

如果服务器调用花费太多时间,则会向用户指示数据正在加载。

我们用它 mat-progress-bar 来表示表记录的加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<h2>mat-table server side pagination</h2>

<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container [matColumnDef]="column" *ngFor="let column of displayedColumns">
<th mat-header-cell *matHeaderCellDef>{{ column }}</th>
<td mat-cell *matCellDef="let emp">{{ emp[column] }}</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let emprow; columns: displayedColumns"></tr>
</table>
<mat-progress-bar mode="indeterminate" *ngIf="isLoading"></mat-progress-bar>
<mat-paginator
#paginator
[length]="totalData"
[pageSizeOptions]="pageSizes"
showFirstLastButtons
></mat-paginator>

并使用 isLoading 变量显示顶部的进度条 mat-paginator。

4. mat-table 服务器端分页示例 StackBlitz Demo

这是 mat-table 分页示例的演示https://stackblitz.com/edit/angular-mat-table-server-side-pagination-example

5. Angular 系列文章

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

6. 参考文档

Server Side Pagination in Angular mat-table using mat-paginator