我们在前面的小节中成功地引用了`HttpClient`并使用其发起了数据请求,最终将请求的数据成功的显示到了界面上。在此过程中,我们普遇到过如下错误:
![](https://img.kancloud.cn/5b/e7/5be7e7416ffc7f6a908954fabdbe2905_575x68.png)
最终通过在测试文件的imports中加入了HttpClientModule将该错误消除。本节我们将重点讨论一下为什么引入了HttpClientModule后错误就消息了。
在继续学习以前,我们暂时移除测试文件`app.component.spec.ts`中对`HttpClientModule`的引入:
```typescript
imports: [
RouterTestingModule,
HttpClientModule ✘
],
```
* ✘ 删除该行
此时`ng t`将报如下错误:
![](https://img.kancloud.cn/57/42/57421d179f3d9392b2078e06c72f4263_606x67.png)
# 错在哪
解决问题的前提是弄明白产生问题的原因,找到问题产生的原因的前提是找到出错代码的位置。为了弄清楚到底是哪行代码导致的异常,我们只保留该测试中的第一行代码:
```typescript
it('组件初始化', () => {
const fixture = TestBed.createComponent(AppComponent);
// const app = fixture.componentInstance;
// expect(app).toBeTruthy();
//
// // 启用angular的自动变更检测机制,自动对V层中的数据进行渲染
// fixture.autoDetectChanges();
});
```
![](https://img.kancloud.cn/57/42/57421d179f3d9392b2078e06c72f4263_606x67.png)
异常依旧,此时我们大概可以确认是第一行代码出现了问题。为了避免误杀,我们在将第一行代码也注释掉:
```typescript
it('组件初始化', () => {
// const fixture = TestBed.createComponent(AppComponent);
// const app = fixture.componentInstance;
// expect(app).toBeTruthy();
//
// 启用angular的自动变更检测机制,自动对V层中的数据进行渲染
// fixture.autoDetectChanges();
});
```
![](https://img.kancloud.cn/1d/74/1d74189bc61a752344c5880ab01acd5c_611x77.png)
错误消失,则可以完全确认是第一行代码出了问题。随后移除刚刚第一行代码的注释后,我们继续学习。
>[success] 当发生一些预期以外的问题时,分块注释是个解决问题的好办法。
## 组件初始化
对组件进行测试的前提是创建一个组件,而创建组件的过程中必然离不开组件的实例化。为了更好的理解这一过程,我们对单元测试中的相关代码并添加相应注释如下:
```typescript
it('组件初始化', () => {
// 创建一个夹具,该夹具中实例化了一个AppComponent对象
const fixture = TestBed.createComponent(AppComponent);
// 获取夹具中的AppComponent对象
// const app = fixture.componentInstance;
// 预测上述过程没有发生错误,即成功获取到了AppComponent对象
// expect(app).toBeTruthy();
// 启用angular的自动变更检测机制,自动对V层中的数据进行渲染
// fixture.autoDetectChanges();
```
`TestBed.createComponent(AppComponent)`的流程图大体如下:
![](https://img.kancloud.cn/9f/87/9f872b7ab8d76cd32159d6bc49cf8722_475x385.png)
通过该图片可以得出结论:错误产生的原因于Angular在能力初始化的过程中没有设置提供`HttpClient`的能力。
而这个能力设置的对象,就是本节中我们将讨论的**模块**。
# 模块
在Angular中**模块**是可独立运行的最小单元,比如我们在src/app文件夹中的app.module.ts的作用就是在声明一个**模块**。
```bash
.
├── app-routing.module.ts
├── app.component.css
├── app.component.html
├── app.component.spec.ts
├── app.component.ts
└── app.module.ts 😀
```
**组件**依赖于**模块**,存在于**模块**,组件若想成动运行,则必然是运行于某个**模块**之中。组件成功运行的前提,是在**模块**中被成功地实例化,**模块**能够成功实例化某个**组件**的前提是**模块**拥有**组件**想要的一切。
![](https://img.kancloud.cn/04/11/041113da69fe9cf930c935442c56486b_336x104.png)
## 测试模块
在单元测试中,我们通过以下代码创建一个测试模块:
```typescript
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule,
],
declarations❶: [
AppComponent❷
],
}).compileComponents();
});
```
通过在❶declarations中声明❷AppComponent来表示:AppComponent组件属于当前测试模块。当单元测试发生错误时,我们可以查看到当前测试模块的身影。
![](https://img.kancloud.cn/bb/ca/bbcae41a724713bf0ebe6137734942e4_566x42.png)
它的名称为DynamicTestModule,即动态测试模块,即为当前AppComponent组件所在模块。
一个**模块**拥有什么样的能力,除可以在`declarations`中声明自身拥有的能力以外,还可以在`imports`引用外部的资源。比如当前引入了`RouterTestingModule`,则当前模块拥有`RouterTestingModule`提供的相关能力。
```typescript
imports: [
RouterTestingModule,
],
```
由于我们要测试的`AppComponent`并不依赖于任何`RouterTestingModule`提供的能力,所以在此我们可以将其引入的代码删除。
```typescript
import {RouterTestingModule} from '@angular/router/testing'; ✘
...
imports: [
RouterTestingModule, ✘
],
```
## HttpClientModule
`declarations`与`imports`不同的是,前者仅能够声明`组件`等**模块**的成员,而后者则是声明的其它**模块**。
与当前**动态测试模块**相同,**HttpClientModule**也是一个模块,该模块下有一个**可用**的**HttpClient**成员,所以当我们将`HttpClientModule`加入到`imports`中时:
```typescript
imports: [
HttpClientModule
],
```
相当于:
![](https://img.kancloud.cn/0d/e3/0de386bb8e7c287381f26c3dad004b50_391x181.png)
此时动态测试模块便拥有了两个成员:自己声明的`APP组件`以及引入的`HttpClientModule`模块中的`HttpClient`。所以此时在执行`TestBed.createComponent(AppComponent)`当前模块便拥有了创建`AppComponent`的先决条件。从而使得在动态测试模块中成功的创建了`AppComponent`
最后我们忧愁单元测试相关注释内容:
```typescript
it('组件初始化', () => {
// 创建一个夹具,该夹具中实例化了一个AppComponent对象
const fixture = TestBed.createComponent(AppComponent);
// 获取夹具中的AppComponent对象
const app = fixture.componentInstance;
// 预测上述过程没有发生错误,即成功获取到了AppComponent对象
expect(app).toBeTruthy();
// 启用angular的自动变更检测机制,自动对V层中的数据进行渲染
fixture.autoDetectChanges();
```
# 依赖注入
依赖注入(Dependency Injection)简称DI,是大部分框架支持的特性。好像已然成为了面试的必考题了一样。在此,我们简单对此有个介绍。
![](https://img.kancloud.cn/9f/87/9f872b7ab8d76cd32159d6bc49cf8722_475x385.png)
简单来说:上图中的❸❹❻❼的过程称即被称为**依赖注入**。
因为❸❹❻❼过程大体实现了:根据AppComponent声明的**依赖**类型`HttpClient`来将一个`HttpClient`实例**注入**给AppComponent。
更准确的来讲,**依赖**一词源于UML类图中的类与类的一种关系,表示两个类之间存在的一种弱关系,是两个类耦合最小的一种。比如人类当前依赖于手机、依赖于网络,都是现实生活中的依赖的具体体现。
**注入**一词就是给的意思,但这个给的操作是Angular主动实施的,是自上而上的,所以称为**注入**更恰当一些。
# 总结
让我们简单总结下:
* 组件不能够独立存在,其必然属于某个**模块**
* 一个模块的**能力**取决于两个方面:
* 其`declarations`的声明的成员
* 其`imports`中声明模块的**可用**成员
* 一个模块的能力列表决定了其是否有创建某个组件的能力
# 资源列表
| 名称 | 地址 |
|---- | ---- |
| Angular模块简介 | [https://www.angular.cn/guide/ngmodules](https://www.angular.cn/guide/ngmodules) |
| Angular中的依赖注入 | [https://www.angular.cn/guide/dependency-injection](https://www.angular.cn/guide/dependency-injection) |
| 本节源码 | [https://github.com/mengyunzhi/angular11-guild/archive/step2.2.3.zip](https://github.com/mengyunzhi/angular11-guild/archive/step2.2.3.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 发布部署
- 第九章 总结