當(dāng)前我們實(shí)現(xiàn)了3個(gè)靜態(tài)的分頁(yè),很明顯這是有問(wèn)題的。在現(xiàn)實(shí)的項(xiàng)目中,不可能固定的有3內(nèi)容,也就是說(shuō)頁(yè)碼必然是動(dòng)態(tài)變化的。而我們則需要適應(yīng)各種變化。一個(gè)比較簡(jiǎn)單的辦法是根據(jù)總頁(yè)數(shù)來(lái)建立一個(gè)數(shù)組,然后在V層中循環(huán)這個(gè)數(shù)組:
## 分頁(yè)數(shù)組
我們?nèi)匀皇褂胹tep by step的方式來(lái)開(kāi)發(fā)動(dòng)態(tài)分頁(yè)功能,在此先在C層新建一個(gè)分頁(yè)數(shù)組:
```typescript
+++ b/first-app/src/app/clazz/clazz.component.ts
@@ -16,6 +16,9 @@ export class ClazzComponent implements OnInit {
// 每頁(yè)默認(rèn)為3條
size = 3;
+ // 分頁(yè)數(shù)組
+ pages = [0, 1, 2, 3, 4, 5, 6, 7, 8];
+
// 初始化一個(gè)有0條數(shù)據(jù)的
pageData = new Page<Clazz>({
```
然后在C層來(lái)循環(huán)這個(gè)數(shù)組,當(dāng)前V層分頁(yè)代碼如下:
```html
<ul class="pagination col-md-auto">
<li [ngClass]="{disabled: page === 0}" class="page-item"><span class="page-link">上一頁(yè)</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">下一頁(yè)</span></li>
</ul>
```
將原頁(yè)碼變更為循環(huán)輸出,變更后代碼如下:
```html
<ul class="pagination col-md-auto">
<li [ngClass]="{disabled: page === 0}" class="page-item"><span class="page-link">上一頁(yè)</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">下一頁(yè)</span></li>
</ul>
```
上述代碼對(duì)`pages`進(jìn)行了循環(huán),使用`ngClass`來(lái)設(shè)置了`class`值,將`p`的值傳入了`onPage()`方法,最后使用插值表達(dá)式來(lái)輸出了當(dāng)前頁(yè)碼(1基)。

## 生成數(shù)組
根據(jù)數(shù)組動(dòng)態(tài)生成分頁(yè)后,便可以再依據(jù)httpClient的返回來(lái)動(dòng)態(tài)地生成分頁(yè)數(shù)組了:
```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);
+ // 根據(jù)返回的值生成分頁(yè)數(shù)組
+ this.pages = [];
+ // for (let i = 0; i < pageData.總頁(yè)數(shù); i++) {
+ // this.pages.push(i);
+ // }
});
}
}
```
如上,如果pageData上存在一個(gè)`總頁(yè)數(shù)`,便能實(shí)現(xiàn)當(dāng)下的功能。寫(xiě)到這里,突然的發(fā)現(xiàn)在前面寫(xiě)`Page`類時(shí),由于自己的疏忽忽略了代表**總頁(yè)數(shù)**的`totalPages`屬性。
### 解決BUG
找到`entity`文件夾中的`Page`類,添加`totalPages`屬性,并在構(gòu)造函數(shù)中完成對(duì)其的初始化:
```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()實(shí)現(xiàn)上取整,比如共10條記錄,每頁(yè)6條,則 10 / 6 = 1.x
+ // Math.ceil(1.x) = 2 得出共2頁(yè)
+ this.totalPages = Math.ceil(this.numberOfElements / this.size);
+ }
}
}
```
測(cè)試代碼如下:
```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頁(yè),首頁(yè)
page = new Page({
number: 0,
size: 20,
numberOfElements: 192,
content: []
});
expect(page.first).toBeTrue();
expect(page.last).toBeFalse();
+ expect(page.totalPages).toBe(10);
// 共41條數(shù)據(jù),當(dāng)前第3頁(yè),每頁(yè)20條,所以當(dāng)前頁(yè)為尾頁(yè)
page = new Page({
number: 2,
size: 20,
numberOfElements: 41,
content: []
});
expect(page.first).toBeFalse();
expect(page.last).toBeTrue();
+ expect(page.totalPages).toBe(3);
});
```
## 使用總頁(yè)數(shù)
分頁(yè)信息中存在總頁(yè)數(shù)后,便可以取消C層相應(yīng)的注釋,從而完成根據(jù)總頁(yè)數(shù)來(lái)動(dòng)態(tài)生成分頁(yè)數(shù)組了:
```typescript
+++ b/first-app/src/app/clazz/clazz.component.ts
@@ -47,9 +47,9 @@ export class ClazzComponent implements OnInit {
console.log(pageData);
// 根據(jù)返回的值生成分頁(yè)數(shù)組
this.pages = [];
- // for (let i = 0; i < pageData.總頁(yè)數(shù); i++) {
- // this.pages.push(i);
- // }
+ for (let i = 0; i < pageData.totalPages; i++) {
+ this.pages.push(i);
+ }
});
}
}
```
此時(shí)當(dāng)我們點(diǎn)擊分頁(yè)信息時(shí),將會(huì)重新請(qǐng)求后臺(tái),并按后臺(tái)返回的數(shù)據(jù)情況動(dòng)態(tài)生成分頁(yè):

## 代碼修正重構(gòu)
最后讓我們對(duì)代碼進(jìn)行修正,以移除開(kāi)發(fā)痕跡。然后進(jìn)行重構(gòu),以不制造重復(fù)的輪子。
### 修正
我們初始化了大小為9的分頁(yè)數(shù)組,當(dāng)初這樣做是為了分步開(kāi)發(fā),此時(shí)我們將其修改為空數(shù)組:
```typescript
// 分頁(yè)數(shù)組
- pages = [0, 1, 2, 3, 4, 5, 6, 7, 8];
+ pages = [] as number[];
```
當(dāng)把一個(gè)數(shù)組初始化為空數(shù)組時(shí),由于數(shù)組中是空的,所以typescript無(wú)法推斷出我們?yōu)閿?shù)組指定的元素的類型(比如我們當(dāng)前想初始化一個(gè)存放number的類型),在此使用`as number[]`來(lái)告知typescript數(shù)組中存放元素的類型為number。
### 重構(gòu)
我們`ngOnInit()`及`onPage()`方法中,使用不同的`page`調(diào)用了相同的后臺(tái)地址,這導(dǎo)致兩個(gè)方法中的代碼大體相同。由于重復(fù)代碼的存在,使得增加某些功能時(shí)要進(jìn)行多次處理。比如接收到后臺(tái)的分頁(yè)數(shù)據(jù)后,根據(jù)總頁(yè)碼數(shù)初始化分頁(yè)數(shù)組。
如果重構(gòu)為一個(gè)方法,則會(huì)使日后的修改、維護(hù)變得更簡(jiǎn)單,為此在組件中創(chuàng)建一個(gè)新的方法,并接修收`page`參數(shù):
```typescript
loadByPage(page = 0): void {
}
```
上述代碼中我們使用`page = 0`的方式為參數(shù)`page`設(shè)置了一個(gè)默認(rèn)值。接著將`onPage()`的方法體內(nèi)容遷移過(guò)來(lái):
```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 => {
// 在請(qǐng)求數(shù)據(jù)之后設(shè)置當(dāng)前頁(yè)
this.page = page;
this.pageData = pageData;
console.log(pageData);
// 根據(jù)返回的值生成分頁(yè)數(shù)組
this.pages = [];
for (let i = 0; i < pageData.totalPages; i++) {
this.pages.push(i);
}
});
}
```
最后刪除原`ngOnInt()`、`onPage()`方法中原有的代碼,添加對(duì)`loadByName()`方法的調(diào)用:
```typescript
ngOnInit(): void {
// 使用默認(rèn)值 page = 0 調(diào)用loadByPage()方法
this.loadByPage();
}
onPage(page: number): void {
this.loadByPage(page);
}
```
如此以來(lái)`loadByPage()`則是我們?cè)斓奈ㄒ坏妮喿樱@使日后修改、維護(hù)起來(lái)都會(huì)更加簡(jiǎn)單。
### disable
最后完成上一頁(yè)、下一頁(yè)的disabled。利用后臺(tái)返回的`pageData`,這個(gè)功能變得很簡(jiǎn)單:如果是第一頁(yè),則disabled上一頁(yè);如果是最后一頁(yè),則disabled下一頁(yè):
```html
<ul class="pagination col-md-auto">
- <li [ngClass]="{disabled: page === 0}" class="page-item"><span class="page-link">上一頁(yè)</span></li>
+ <li [ngClass]="{disabled: pageData.first}" class="page-item"><span class="page-link">上一頁(yè)</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">下一頁(yè)</span></li>
+ <li [ngClass]="{disabled: pageData.last}" class="page-item"><span class="page-link">下一頁(yè)</span></li>
</ul>
</nav>
```
好了,愉悅的欣賞自己的杰作吧。
## 本節(jié)作業(yè)
1. 恢復(fù)代碼至上節(jié)的內(nèi)容,完成動(dòng)態(tài)分頁(yè)功能
2. 當(dāng)前ClazzMockApi在返回總頁(yè)數(shù)時(shí),返回的是10。將其修改為8,15,100試試
3. 請(qǐng)思索當(dāng)前分頁(yè)功能還存哪些不完善的地方,憑自己的能力嘗試完善它
| 名稱 | 鏈接 |
| -------- | ------------------------------------------------------------ |
| 本節(jié)源碼 | [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 環(huán)境安裝
- 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 請(qǐng)求后臺(tái)數(shù)據(jù)
- 2.2.1 HttpClient
- 2.2.2 請(qǐng)求數(shù)據(jù)
- 2.2.3 模塊與依賴注入
- 2.2.4 異步與回調(diào)函數(shù)
- 2.2.5 集成測(cè)試
- 2.2.6 本章小節(jié)
- 2.3 新增教師
- 2.3.1 組件初始化
- 2.3.2 [(ngModel)]
- 2.3.3 對(duì)接后臺(tái)
- 2.3.4 路由
- 2.4 編輯教師
- 2.4.1 組件初始化
- 2.4.2 獲取路由參數(shù)
- 2.4.3 插值與模板表達(dá)式
- 2.4.4 初識(shí)泛型
- 2.4.5 更新教師
- 2.4.6 測(cè)試中的路由
- 2.5 刪除教師
- 2.6 收尾工作
- 2.6.1 RouterLink
- 2.6.2 fontawesome圖標(biāo)庫(kù)
- 2.6.3 firefox
- 2.7 總結(jié)
- 第三章 用戶登錄
- 3.1 初識(shí)單元測(cè)試
- 3.2 http概述
- 3.3 Basic access authentication
- 3.4 著陸組件
- 3.5 @Output
- 3.6 TypeScript 類
- 3.7 瀏覽器緩存
- 3.8 總結(jié)
- 第四章 個(gè)人中心
- 4.1 原型
- 4.2 管道
- 4.3 對(duì)接后臺(tái)
- 4.4 x-auth-token認(rèn)證
- 4.5 攔截器
- 4.6 小結(jié)
- 第五章 系統(tǒng)菜單
- 5.1 延遲及測(cè)試
- 5.2 手動(dòng)創(chuàng)建組件
- 5.3 隱藏測(cè)試信息
- 5.4 規(guī)劃路由
- 5.5 定義菜單
- 5.6 注銷
- 5.7 小結(jié)
- 第六章 班級(jí)管理
- 6.1 新增班級(jí)
- 6.1.1 組件初始化
- 6.1.2 MockApi 新建班級(jí)
- 6.1.3 ApiInterceptor
- 6.1.4 數(shù)據(jù)驗(yàn)證
- 6.1.5 教師選擇列表
- 6.1.6 MockApi 教師列表
- 6.1.7 代碼重構(gòu)
- 6.1.8 小結(jié)
- 6.2 教師列表組件
- 6.2.1 初始化
- 6.2.2 響應(yīng)式表單
- 6.2.3 getTestScheduler()
- 6.2.4 應(yīng)用組件
- 6.2.5 小結(jié)
- 6.3 班級(jí)列表
- 6.3.1 原型設(shè)計(jì)
- 6.3.2 初始化分頁(yè)
- 6.3.3 MockApi
- 6.3.4 靜態(tài)分頁(yè)
- 6.3.5 動(dòng)態(tài)分頁(yè)
- 6.3.6 @Input()
- 6.4 編輯班級(jí)
- 6.4.1 測(cè)試模塊
- 6.4.2 響應(yīng)式表單驗(yàn)證
- 6.4.3 @Input()
- 6.4.4 FormGroup
- 6.4.5 自定義FormControl
- 6.4.6 代碼重構(gòu)
- 6.4.7 小結(jié)
- 6.5 刪除班級(jí)
- 6.6 集成測(cè)試
- 6.6.1 惰性加載
- 6.6.2 API攔截器
- 6.6.3 路由與跳轉(zhuǎn)
- 6.6.4 ngStyle
- 6.7 初識(shí)Service
- 6.7.1 catchError
- 6.7.2 單例服務(wù)
- 6.7.3 單元測(cè)試
- 6.8 小結(jié)
- 第七章 學(xué)生管理
- 7.1 班級(jí)列表組件
- 7.2 新增學(xué)生
- 7.2.1 exports
- 7.2.2 自定義驗(yàn)證器
- 7.2.3 異步驗(yàn)證器
- 7.2.4 再識(shí)DI
- 7.2.5 屬性型指令
- 7.2.6 完成功能
- 7.2.7 小結(jié)
- 7.3 單元測(cè)試進(jìn)階
- 7.4 學(xué)生列表
- 7.4.1 JSON對(duì)象與對(duì)象
- 7.4.2 單元測(cè)試
- 7.4.3 分頁(yè)模塊
- 7.4.4 子組件測(cè)試
- 7.4.5 重構(gòu)分頁(yè)
- 7.5 刪除學(xué)生
- 7.5.1 第三方dialog
- 7.5.2 批量刪除
- 7.5.3 面向?qū)ο?/a>
- 7.6 集成測(cè)試
- 7.7 編輯學(xué)生
- 7.7.1 初始化
- 7.7.2 自定義provider
- 7.7.3 更新學(xué)生
- 7.7.4 集成測(cè)試
- 7.7.5 可訂閱的路由參數(shù)
- 7.7.6 小結(jié)
- 7.8 總結(jié)
- 第八章 其它
- 8.1 打包構(gòu)建
- 8.2 發(fā)布部署
- 第九章 總結(jié)