當前我們實現了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基)。

## 生成數組
根據數組動態生成分頁后,便可以再依據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);
+ }
});
}
}
```
此時當我們點擊分頁信息時,將會重新請求后臺,并按后臺返回的數據情況動態生成分頁:

## 代碼修正重構
最后讓我們對代碼進行修正,以移除開發痕跡。然后進行重構,以不制造重復的輪子。
### 修正
我們初始化了大小為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 發布部署
- 第九章 總結