当前我们实现了3个静态的分页,很明显这是有问题的。在现实的项目中,不可能固定的有3内容,也就是说页码必然是动态变化的。而我们则需要适应各种变化。一个比较简单的办法是根据总页数来建立一个数组,然后在V层中循环这个数组:
## 分页数组
我们仍然使用step by step的方式来开发动态分页功能,在此先在C层新建一个分页数组:
```typescript
+++ b/first-app/src/app/clazz/clazz.component.ts
@@ -16,6 +16,9 @@ export class ClazzComponent implements OnInit {
// 每页默认为3条
size = 3;
+ // 分页数组
+ pages = [0, 1, 2, 3, 4, 5, 6, 7, 8];
+
// 初始化一个有0条数据的
pageData = new Page<Clazz>({
```
然后在C层来循环这个数组,当前V层分页代码如下:
```html
<ul class="pagination col-md-auto">
<li [ngClass]="{disabled: page === 0}" class="page-item"><span class="page-link">上一页</span></li>
<li [ngClass]="{active: page === 0}" class="page-item"><span class="page-link" (click)="onPage(0)">1</span></li>
<li [ngClass]="{active: page === 1}" class="page-item"><span class="page-link" (click)="onPage(1)">2</span></li>
<li [ngClass]="{active: page === 2}" class="page-item"><span class="page-link" (click)="onPage(2)">3</span></li>
<li [ngClass]="{disabled: page === 2}" class="page-item"><span class="page-link">下一页</span></li>
</ul>
```
将原页码变更为循环输出,变更后代码如下:
```html
<ul class="pagination col-md-auto">
<li [ngClass]="{disabled: page === 0}" class="page-item"><span class="page-link">上一页</span></li>
<li *ngFor="let p of pages" [ngClass]="{active: page === p}" class="page-item">
<span class="page-link" (click)="onPage(p)">{{p + 1}}</span>
</li>
<li [ngClass]="{disabled: page === 2}" class="page-item"><span class="page-link">下一页</span></li>
</ul>
```
上述代码对`pages`进行了循环,使用`ngClass`来设置了`class`值,将`p`的值传入了`onPage()`方法,最后使用插值表达式来输出了当前页码(1基)。
![image-20210331110849794](https://img.kancloud.cn/2d/56/2d56580084cbeafc5b811e0a99343244_1020x140.png)
## 生成数组
根据数组动态生成分页后,便可以再依据httpClient的返回来动态地生成分页数组了:
```typescript
+++ b/first-app/src/app/clazz/clazz.component.ts
@@ -45,6 +45,11 @@ export class ClazzComponent implements OnInit {
this.page = page;
this.pageData = pageData;
console.log(pageData);
+ // 根据返回的值生成分页数组
+ this.pages = [];
+ // for (let i = 0; i < pageData.总页数; i++) {
+ // this.pages.push(i);
+ // }
});
}
}
```
如上,如果pageData上存在一个`总页数`,便能实现当下的功能。写到这里,突然的发现在前面写`Page`类时,由于自己的疏忽忽略了代表**总页数**的`totalPages`属性。
### 解决BUG
找到`entity`文件夹中的`Page`类,添加`totalPages`属性,并在构造函数中完成对其的初始化:
```typescript
+++ b/first-app/src/app/entity/page.ts
@@ -9,6 +9,7 @@ export class Page<T> {
size: number;
numberOfElements: number;
first: boolean;
+ totalPages: number;
constructor(data: {
content: T[],
@@ -16,7 +17,8 @@ export class Page<T> {
number: number,
size: number,
numberOfElements: number,
- first?: boolean
+ first?: boolean,
+ totalPages?: number;
}) {
this.content = data.content;
this.number = data.number;
@@ -33,5 +35,13 @@ export class Page<T> {
} else {
this.first = this.number === 0 ? true : false;
}
+
+ if (data.totalPages !== undefined) {
+ this.totalPages = data.totalPages;
+ } else {
+ // Math.ceil()实现上取整,比如共10条记录,每页6条,则 10 / 6 = 1.x
+ // Math.ceil(1.x) = 2 得出共2页
+ this.totalPages = Math.ceil(this.numberOfElements / this.size);
+ }
}
}
```
测试代码如下:
```typescript
it('should create an instance', () => {
// 不加入last, first初始化
let page = new Page({
number: 2,
size: 20,
numberOfElements: 200,
content: []
});
expect(page).toBeTruthy();
expect(page.first).toBeFalse();
expect(page.last).toBeFalse();
+ expect(page.totalPages).toBe(10);
// 第1页,首页
page = new Page({
number: 0,
size: 20,
numberOfElements: 192,
content: []
});
expect(page.first).toBeTrue();
expect(page.last).toBeFalse();
+ expect(page.totalPages).toBe(10);
// 共41条数据,当前第3页,每页20条,所以当前页为尾页
page = new Page({
number: 2,
size: 20,
numberOfElements: 41,
content: []
});
expect(page.first).toBeFalse();
expect(page.last).toBeTrue();
+ expect(page.totalPages).toBe(3);
});
```
## 使用总页数
分页信息中存在总页数后,便可以取消C层相应的注释,从而完成根据总页数来动态生成分页数组了:
```typescript
+++ b/first-app/src/app/clazz/clazz.component.ts
@@ -47,9 +47,9 @@ export class ClazzComponent implements OnInit {
console.log(pageData);
// 根据返回的值生成分页数组
this.pages = [];
- // for (let i = 0; i < pageData.总页数; i++) {
- // this.pages.push(i);
- // }
+ for (let i = 0; i < pageData.totalPages; i++) {
+ this.pages.push(i);
+ }
});
}
}
```
此时当我们点击分页信息时,将会重新请求后台,并按后台返回的数据情况动态生成分页:
![image-20210331140301030](https://img.kancloud.cn/ee/ad/eead25a5e5526d8d00cd74f886da140c_1160x150.png)
## 代码修正重构
最后让我们对代码进行修正,以移除开发痕迹。然后进行重构,以不制造重复的轮子。
### 修正
我们初始化了大小为9的分页数组,当初这样做是为了分步开发,此时我们将其修改为空数组:
```typescript
// 分页数组
- pages = [0, 1, 2, 3, 4, 5, 6, 7, 8];
+ pages = [] as number[];
```
当把一个数组初始化为空数组时,由于数组中是空的,所以typescript无法推断出我们为数组指定的元素的类型(比如我们当前想初始化一个存放number的类型),在此使用`as number[]`来告知typescript数组中存放元素的类型为number。
### 重构
我们`ngOnInit()`及`onPage()`方法中,使用不同的`page`调用了相同的后台地址,这导致两个方法中的代码大体相同。由于重复代码的存在,使得增加某些功能时要进行多次处理。比如接收到后台的分页数据后,根据总页码数初始化分页数组。
如果重构为一个方法,则会使日后的修改、维护变得更简单,为此在组件中创建一个新的方法,并接修收`page`参数:
```typescript
loadByPage(page = 0): void {
}
```
上述代码中我们使用`page = 0`的方式为参数`page`设置了一个默认值。接着将`onPage()`的方法体内容迁移过来:
```typescript
loadByPage(page = 0): void {
const httpParams = new HttpParams().append('page', page.toString())
.append('size', this.size.toString());
this.httpClient.get<Page<Clazz>>('/clazz/page', {params: httpParams})
.subscribe(pageData => {
// 在请求数据之后设置当前页
this.page = page;
this.pageData = pageData;
console.log(pageData);
// 根据返回的值生成分页数组
this.pages = [];
for (let i = 0; i < pageData.totalPages; i++) {
this.pages.push(i);
}
});
}
```
最后删除原`ngOnInt()`、`onPage()`方法中原有的代码,添加对`loadByName()`方法的调用:
```typescript
ngOnInit(): void {
// 使用默认值 page = 0 调用loadByPage()方法
this.loadByPage();
}
onPage(page: number): void {
this.loadByPage(page);
}
```
如此以来`loadByPage()`则是我们造的唯一的轮子,这使日后修改、维护起来都会更加简单。
### disable
最后完成上一页、下一页的disabled。利用后台返回的`pageData`,这个功能变得很简单:如果是第一页,则disabled上一页;如果是最后一页,则disabled下一页:
```html
<ul class="pagination col-md-auto">
- <li [ngClass]="{disabled: page === 0}" class="page-item"><span class="page-link">上一页</span></li>
+ <li [ngClass]="{disabled: pageData.first}" class="page-item"><span class="page-link">上一页</span></li>
<li *ngFor="let p of pages" [ngClass]="{active: page === p}" class="page-item">
<span class="page-link" (click)="onPage(p)">{{p + 1}}</span>
</li>
- <li [ngClass]="{disabled: page === 2}" class="page-item"><span class="page-link">下一页</span></li>
+ <li [ngClass]="{disabled: pageData.last}" class="page-item"><span class="page-link">下一页</span></li>
</ul>
</nav>
```
好了,愉悦的欣赏自己的杰作吧。
## 本节作业
1. 恢复代码至上节的内容,完成动态分页功能
2. 当前ClazzMockApi在返回总页数时,返回的是10。将其修改为8,15,100试试
3. 请思索当前分页功能还存哪些不完善的地方,凭自己的能力尝试完善它
| 名称 | 链接 |
| -------- | ------------------------------------------------------------ |
| 本节源码 | [https://github.com/mengyunzhi/angular11-guild/archive/step6.3.5.zip](https://github.com/mengyunzhi/angular11-guild/archive/step6.3.5.zip) |
- 序言
- 第一章 Hello World
- 1.1 环境安装
- 1.2 Hello Angular
- 1.3 Hello World!
- 第二章 教师管理
- 2.1 教师列表
- 2.1.1 初始化原型
- 2.1.2 组件生命周期之初始化
- 2.1.3 ngFor
- 2.1.4 ngIf、ngTemplate
- 2.1.5 引用 Bootstrap
- 2.2 请求后台数据
- 2.2.1 HttpClient
- 2.2.2 请求数据
- 2.2.3 模块与依赖注入
- 2.2.4 异步与回调函数
- 2.2.5 集成测试
- 2.2.6 本章小节
- 2.3 新增教师
- 2.3.1 组件初始化
- 2.3.2 [(ngModel)]
- 2.3.3 对接后台
- 2.3.4 路由
- 2.4 编辑教师
- 2.4.1 组件初始化
- 2.4.2 获取路由参数
- 2.4.3 插值与模板表达式
- 2.4.4 初识泛型
- 2.4.5 更新教师
- 2.4.6 测试中的路由
- 2.5 删除教师
- 2.6 收尾工作
- 2.6.1 RouterLink
- 2.6.2 fontawesome图标库
- 2.6.3 firefox
- 2.7 总结
- 第三章 用户登录
- 3.1 初识单元测试
- 3.2 http概述
- 3.3 Basic access authentication
- 3.4 着陆组件
- 3.5 @Output
- 3.6 TypeScript 类
- 3.7 浏览器缓存
- 3.8 总结
- 第四章 个人中心
- 4.1 原型
- 4.2 管道
- 4.3 对接后台
- 4.4 x-auth-token认证
- 4.5 拦截器
- 4.6 小结
- 第五章 系统菜单
- 5.1 延迟及测试
- 5.2 手动创建组件
- 5.3 隐藏测试信息
- 5.4 规划路由
- 5.5 定义菜单
- 5.6 注销
- 5.7 小结
- 第六章 班级管理
- 6.1 新增班级
- 6.1.1 组件初始化
- 6.1.2 MockApi 新建班级
- 6.1.3 ApiInterceptor
- 6.1.4 数据验证
- 6.1.5 教师选择列表
- 6.1.6 MockApi 教师列表
- 6.1.7 代码重构
- 6.1.8 小结
- 6.2 教师列表组件
- 6.2.1 初始化
- 6.2.2 响应式表单
- 6.2.3 getTestScheduler()
- 6.2.4 应用组件
- 6.2.5 小结
- 6.3 班级列表
- 6.3.1 原型设计
- 6.3.2 初始化分页
- 6.3.3 MockApi
- 6.3.4 静态分页
- 6.3.5 动态分页
- 6.3.6 @Input()
- 6.4 编辑班级
- 6.4.1 测试模块
- 6.4.2 响应式表单验证
- 6.4.3 @Input()
- 6.4.4 FormGroup
- 6.4.5 自定义FormControl
- 6.4.6 代码重构
- 6.4.7 小结
- 6.5 删除班级
- 6.6 集成测试
- 6.6.1 惰性加载
- 6.6.2 API拦截器
- 6.6.3 路由与跳转
- 6.6.4 ngStyle
- 6.7 初识Service
- 6.7.1 catchError
- 6.7.2 单例服务
- 6.7.3 单元测试
- 6.8 小结
- 第七章 学生管理
- 7.1 班级列表组件
- 7.2 新增学生
- 7.2.1 exports
- 7.2.2 自定义验证器
- 7.2.3 异步验证器
- 7.2.4 再识DI
- 7.2.5 属性型指令
- 7.2.6 完成功能
- 7.2.7 小结
- 7.3 单元测试进阶
- 7.4 学生列表
- 7.4.1 JSON对象与对象
- 7.4.2 单元测试
- 7.4.3 分页模块
- 7.4.4 子组件测试
- 7.4.5 重构分页
- 7.5 删除学生
- 7.5.1 第三方dialog
- 7.5.2 批量删除
- 7.5.3 面向对象
- 7.6 集成测试
- 7.7 编辑学生
- 7.7.1 初始化
- 7.7.2 自定义provider
- 7.7.3 更新学生
- 7.7.4 集成测试
- 7.7.5 可订阅的路由参数
- 7.7.6 小结
- 7.8 总结
- 第八章 其它
- 8.1 打包构建
- 8.2 发布部署
- 第九章 总结