当前我们实现了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) |