# ?教師選擇列表
前面的小節中選擇班主任時,我們手寫了張三、李四兩個教師信息。顯然這與實際的需求是不相符的。在教師選擇組件中,我們希望顯示當前系統的所有教師。
本節我們將在MockApi的支持下,模擬由后臺獲取所有教師數據,然后在此基礎上,打造一個真實的教師選擇列表。
## 初始化
開發新的組件離不開原型的初始化,開發新功能也是如此,這有利于我們掌握真實的功能需求。
## C層初始化
教師列表組件需要顯示所有的教師信息,所以我們在C層初始化一個教師的數組:
```typescript
+++ b/first-app/src/app/clazz/add/add.component.ts
@@ -13,6 +13,8 @@ export class AddComponent implements OnInit {
teacherId: null as unknown as number
};
+ teachers = [];
+
constructor(private httpClient: HttpClient) {
}
```
既然是初始化,我們還是希望能夠看到一些效果的。根據當前V層的教師列表相關代碼:
```html
<select id="teacher" class="form-control"
name="teacher"
[(ngModel)]="clazz.teacherId">
<option [ngValue]="1">張三</option>
<option [ngValue]="2">李四</option>
</select>
```
我們確認一個教師列表選擇功能,需要教師的兩個屬性:1. 教師id;2.教師姓名。據此,我們在C層中初始化這兩項信息:
```typescript
- teachers = [];
+ teachers = [
+ {
+ id: 1,
+ name: '張三'
+ },
+ {
+ id: 2,
+ name: '李四'
+ }
+ ];
```
直接使用`[]`來對數組初始化顯示比較方法,但這樣做卻使TypeScript強類型的優勢完全失效。所以在定義數組時,正確的做法應該是 `teachers = new Array<Teacher>;`
```typescript
import {Teacher} from '../../entity/teacher';
teachers = new Array<Teacher>(
{
id: 1,
name: '張三'
},
{
id: 2,
name: '李四'
}
);
```
TypeScript的強類型能夠防止我們犯一些拼寫方面的錯誤,比如我們在此將數組的元素類型約束為Teacher后,會在IDE下發現如下錯誤提醒:

這是在告訴我們:當前數組中的元素類型應該為Teacher,則其中的每一項的類型都應該是Teacher。Teacher的類型中除了`id`,`name`屬性以外,還存在著`email` 、`password`、`sex`、`username`屬性。
消除該提示有多種方法,最簡單方法是加入`email`等屬性,比如:
```typescript
{
id: 1,
name: '張三',
email: 'zhangsan@yunzhi.club',
username: 'zhangsan',
sex: true,
password: ''
},
```
但是我們明明只需要`id`,`name`就可以了,初始化時加入其它不需要的屬性便是畫蛇添足了。在這種僅需要某幾個屬性的情況下,我們往往使用`as`來進行類型的轉換來消除此類錯誤。
```typescript
teachers = new Array<Teacher>(
{
id: 1,
name: '張三'
} as Teacher,
{
id: 2,
name: '李四'
} as Teacher
);
```
## V層初始化
V層的實現則可以使用我們前面學習過的`ngFor`指令:
```html
+++ b/first-app/src/app/clazz/add/add.component.html
@@ -16,8 +16,9 @@
<select id="teacher" class="form-control"
name="teacher"
[(ngModel)]="clazz.teacherId">
- <option [ngValue]="1">張三</option>
- <option [ngValue]="2">李四</option>
+ <option *ngFor="let teacher of teachers" [ngValue]="teacher.id">
+ {{teacher.name}}
+ </option>
</select>
<small class="text-danger"
*ngIf="clazz.teacherId === null">
```
## 測試
使用`ng t`在`add.component.spec.ts`或`add.component.mock-api.spec.ts`的支持下,對上述代碼進行測試。比如我們以`add.component.spec.ts`做為測試文件,將`it`變更為`fit`:
```typescript
fit('should create', () => {
expect(component).toBeTruthy();
fixture.autoDetectChanges();
});
```

在開發某個組件時,應該時刻保持僅有一個測試用例生效,也就是整個項目中,最多存在一個`fit`。

## 對接后臺
初始化完成了、效果也有了,接下來便是正式的對接后臺。既然是正式對接后臺,那么C層中則僅需要初始化一個空的教師數組即可:
```typescript
+++ b/first-app/src/app/clazz/add/add.component.ts
@@ -14,16 +14,7 @@ export class AddComponent implements OnInit {
teacherId: null as unknown as number
};
- teachers = new Array<Teacher>(
- {
- id: 1,
- name: '張三'
- } as Teacher,
- {
- id: 2,
- name: '李四'
- } as Teacher
- );
+ teachers = new Array<Teacher>();
constructor(private httpClient: HttpClient) {
}
```
接下來調用后臺API獲取全部教師。獲取所有教師的API我們在前面的章節中已經給出了,為`GET /teacher`。在Angular中,若想在組件初始化后執行某些代碼,則僅需要將該代碼添加到`ngOnInit`方法中(這也是已經講過的知識,相信你一定還記得):
```typescript
+++ b/first-app/src/app/clazz/add/add.component.ts
@@ -20,6 +20,9 @@ export class AddComponent implements OnInit {
}
ngOnInit(): void {
+ // 獲取所有教師
+ this.httpClient.get('teacher')
+ .subscribe(teachers => this.teachers = teachers);
}
```
上述代碼從理論上來講是沒有任何問題的:我們通過GET方法來獲取全部的教師數據,然后在將其賦值給當前組件的teachers屬性。但是TypeScript卻讀不懂我們心中所想,由于`httpClient.get`返回的數據類型被定義為`Object`,所以以下代碼實際上是在將一個類型為`Object`的變量賦值為類型為`Array<Teacher>`的變量。
```typescript
this.httpClient.get('teacher')
.subscribe(teachers 類型為Object) => this.teachers 類型為Array<Teacher> = teachers 類型為Object);
```
`Object`類型是個0屬性的對象,比如`{}`的類型便是`Object` ,在面向對象的世界里,它是最**小**的小弟。所以只要是個對象,便可以轉換為Object,而Object卻不能轉換為其它任意非Object類型。
TypeScript聰明的發現了這一點,它在告訴我們說:類型Object這個最小的小弟,怎么能轉換為`Array<Teacher>`這個大哥呢?

為了解決這一問題,httpClient除提供了`get()`方法外,還提供了一個可以指定返回值類型的`get<T>()`:
```typescript
// 獲取所有教師
this.httpClient.get<Array<Teacher>>('teacher')
.subscribe(teachers => this.teachers = teachers);
```
此時TypeScript就明白了:原來在此處進行`get`方法的請求,將得到一個類型為`Array<Teacher>`的返回數據。該類型與當前組件聲明的`teachers`類型相同,錯誤自然也就消失了。
此時在`add.component.spec.ts`測試文件的支持下,打開瀏覽器控制臺,發現發起了真實的網絡請求,并且在教師列表中顯示了后臺擁有的教師列表:


**注意**:我們在這里所說的**返回值**,并不是指函數的返回值,而是特指在發起http請求時,后臺返回來的值。
## Mock測試
我們剛剛非常**幸運**的利用真實的后臺API完成了教師選擇的功能。之所以說幸運,是由于后臺這個接口即不需要先完成用戶登錄,又不需要滿足任何的前提。但真實的生產環境中,我們卻很少碰到這樣的情況。如果是在MockApi的情況下,又該如何測試呢?
為此我們移除`add.component.spec.ts`的`fit`,啟用`add.component.mock-api.spec.ts`的相關測試用例:
```typescript
+++ b/first-app/src/app/clazz/add/add.component.mock-api.spec.ts
@@ -34,7 +34,7 @@ describe('clazz add with mockapi', () => {
component.onSubmit();
});
- it('should create', () => {
+ fit('should create', () => {
fixture.autoDetectChanges();
});
});
```
然后我們簡單思索一會,想像一下可能會發生什么樣的錯誤,又為什么會發生這樣的錯誤。思索完成后打開控制臺來驗證自己的錯誤并嘗試解決。
| 名稱 | 地址 | 備注 |
| ---------------------- | ------------------------------------------------------------ | -------- |
| TypeScript基礎類型 | [https://www.tslang.cn/docs/handbook/basic-types.html](https://www.tslang.cn/docs/handbook/basic-types.html) | 必學內容 |
| 請求輸入一個類型的響應 | [https://angular.cn/guide/http#requesting-a-typed-response](https://angular.cn/guide/http#requesting-a-typed-response) | |
| 本節源碼 | [[https://github.com/mengyunzhi/angular11-guild/archive/step6.1.5.zip](https://github.com/mengyunzhi/angular11-guild/archive/step6.1.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 發布部署
- 第九章 總結