组件初始化后,单元测试如下:
![image-20210412140340871](https://img.kancloud.cn/02/09/02094c24306181be7f64f30e2ca15c5a_1758x192.png)
错误信息提示说以`classId`命名的`FormControl`上没有找到`value accessor`。如果以前报此类错误,我们有些茫然是正常的。但学习过自定义FormControl后,排醒此错误便应该有些眉目了。只所以发生上述错误,是由于在V层中我们使用了如下代码:
```html
<app-clazz-select formControlName="clazzId"></app-clazz-select>
```
上述错误产生的根本原因是当前动态测试模块并不认识`app-clazz-select`,当然就更不知道`app-clazz-select`是我们自定义的`FormControl`了。
## import
我们按下图尝试修正出现的单元测试的错误:
![image-20210412103338742](https://img.kancloud.cn/9d/f5/9df50ef0ec594819c419800866d1fd1b_2282x450.png)
在动态测试模块中引入班级选择模块:
```typescript
+++ b/first-app/src/app/student/add/add.component.spec.ts
@@ -2,6 +2,7 @@ import {ComponentFixture, TestBed} from '@angular/core/testing';
import {AddComponent} from './add.component';
import {ReactiveFormsModule} from '@angular/forms';
+import {ClazzSelectModule} from '../../clazz/clazz-select/clazz-select.module';
describe('student -> AddComponent', () => {
let component: AddComponent;
@@ -11,7 +12,8 @@ describe('student -> AddComponent', () => {
await TestBed.configureTestingModule({
declarations: [AddComponent],
imports: [
- ReactiveFormsModule
+ ReactiveFormsModule,
+ ClazzSelectModule
]
})
```
`import`包括了`ClazzSelectComponent`组件的`ClazzSelectModule`后,错误依旧:`app-clazz-select`仍然未被识别为`FormControl`。
一般这种情况是在创建自己定义组件时没有完全符合以下两种规范造成的:
- 组件未实现`ControlValueAccessor`接口
- 组件未`provider` -> `NG_VALUE_ACCESSOR`
对于有单元测试的我们而言,上述两种情况都不大可能,因为在班级选择组件的单元测试中,我们在父组件中对其进行了测试:
```typescript
first-app/src/app/clazz/clazz-select/clazz-select.component.spec.ts
@Component({
template: `
<app-clazz-select [formControl]="clazzId"></app-clazz-select>`
})
class TestComponent {
clazzId = new FormControl();
}
```
这说明`app-clazz-select`必然是符合`FormControl`规范的。
## 私有与公有
要解决这个问题,就要谈谈模块中的组件、指令和管道的公有**与**私有**了。
默认情况下处于模块`declarations`声明中的组件、指令和管道都是**私有**的,在当前模块的任意组件中可以自由地使用它们。而当模块(A)被`import`进入其它模块(B)时,由于A模块中元素的**私有**性,B模块无法使用A模块中的任意私有元素(组件、指令和管道)。
所以在当前的代码下,进行`imports`时,实际如下图所示:
![image-20210412144236951](https://img.kancloud.cn/75/35/75350e239c5901dd595a0347897a843a_1390x414.png)
如上图所示:私有的班级选择组件并没有成功的并引入到动态测试模块中来,所以动态测试模块并无法解析`app-clazz-select`,当然就更别谈将其做为`FormControl`来看待了。
## exports
若想将Angular中的组件、指令和管道设置为公有的,则需要将其添加到`exports`中:
```typescript
+++ b/first-app/src/app/clazz/clazz-select/clazz-select.module.ts
@@ -9,6 +9,9 @@ import {ReactiveFormsModule} from '@angular/forms';
imports: [
CommonModule,
ReactiveFormsModule
+ ],
+ exports: [
+ ClazzSelectComponent
]
})
export class ClazzSelectModule {
```
此时`ClazzSelectComponent`的属性变更为**公有**,当动态测试模块引入班级选择模块时便开始同步引入了这个班级选择组件了。当然也就认识了`app-clazz-select`,以及感知到其是一个合格的`FormControl`了。
![image-20210412144647751](https://img.kancloud.cn/5b/26/5b261d301d17edd064d3b1d5a76007fd_1390x426.png)
此时不能识别为`FormControl`的错误消失,转而显示新的错误如下:
![image-20210412144938734](https://img.kancloud.cn/02/d5/02d5e34a8517002f5de6b00271708416_1586x162.png)
加入MockApi后错误消失:
```typescript
+++ b/first-app/src/app/student/add/add.component.spec.ts
@@ -3,6 +3,7 @@ import {ComponentFixture, TestBed} from '@angular/core/testing';
import {AddComponent} from './add.component';
import {ReactiveFormsModule} from '@angular/forms';
import {ClazzSelectModule} from '../../clazz/clazz-select/clazz-select.module';
+import {MockApiTestingModule} from '../../mock-api/mock-api-testing.module';
describe('student -> AddComponent', () => {
let component: AddComponent;
@@ -13,7 +14,8 @@ describe('student -> AddComponent', () => {
declarations: [AddComponent],
imports: [
ReactiveFormsModule,
- ClazzSelectModule
+ ClazzSelectModule,
+ MockApiTestingModule
]
})
.compileComponents();
```
最终效果如下:
![image-20210412145049101](https://img.kancloud.cn/11/5c/115c549d2861430b51e831bd9860313d_1164x742.png)
最后,手动的发送后面的模拟数据,并重新渲染V层,以使班级选择组件中默认有数据可选:
```typescript
+++ b/first-app/src/app/student/add/add.component.spec.ts
@@ -30,5 +30,7 @@ describe('student -> AddComponent', () => {
fit('should create', () => {
expect(component).toBeTruthy();
+ getTestScheduler().flush();
+ fixture.detectChanges();
});
});
```
![image-20210412145309498](https://img.kancloud.cn/4b/f6/4bf636333dc19886c487809d5a626097_1180x268.png)
| 名称 | 链接 |
| ---------------- | ------------------------------------------------------------ |
| `imports` 数组 | [https://angular.cn/guide/bootstrapping#the-imports-array](https://angular.cn/guide/bootstrapping#the-imports-array) |
| `exports` | [https://angular.cn/api/core/NgModule#exports](https://angular.cn/api/core/NgModule#exports) |
| 共享特性模块 | [https://angular.cn/guide/sharing-ngmodules#sharing-modules](https://angular.cn/guide/sharing-ngmodules#sharing-modules) |
| 本节源码(含答案) | [https://github.com/mengyunzhi/angular11-guild/archive/step7.2.1.zip](https://github.com/mengyunzhi/angular11-guild/archive/step7.2.1.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 发布部署
- 第九章 总结