# 教师选择列表
前面的小节中选择班主任时,我们手写了张三、李四两个教师信息。显然这与实际的需求是不相符的。在教师选择组件中,我们希望显示当前系统的所有教师。
本节我们将在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下发现如下错误提醒:
![image-20210322101959790](https://img.kancloud.cn/1b/f0/1bf047ec894f3e05866688be47acf6d4_2108x524.png)
这是在告诉我们:当前数组中的元素类型应该为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();
});
```
![image-20210322103940085](https://img.kancloud.cn/2f/67/2f67c22ec0408ed9af79b21ac2940fa1_706x290.png)
在开发某个组件时,应该时刻保持仅有一个测试用例生效,也就是整个项目中,最多存在一个`fit`。
![image-20210322104045306](https://img.kancloud.cn/09/bd/09bde2516aa6e5347dd04a1eb4cf96e0_2410x208.png)
## 对接后台
初始化完成了、效果也有了,接下来便是正式的对接后台。既然是正式对接后台,那么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>`这个大哥呢?
![image-20210322105832384](https://img.kancloud.cn/d8/77/d877f7183b78aa6702b2236f5bbe21a7_2340x266.png)
为了解决这一问题,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`测试文件的支持下,打开浏览器控制台,发现发起了真实的网络请求,并且在教师列表中显示了后台拥有的教师列表:
![image-20210322110456557](https://img.kancloud.cn/df/e9/dfe9e09b58e849c03db59475becbe02b_994x214.png)
![image-20210322104045306](https://img.kancloud.cn/09/bd/09bde2516aa6e5347dd04a1eb4cf96e0_2410x208.png)
**注意**:我们在这里所说的**返回值**,并不是指函数的返回值,而是特指在发起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 发布部署
- 第九章 总结