我們?cè)谇懊娴男」?jié)中成功地引用了`HttpClient`并使用其發(fā)起了數(shù)據(jù)請(qǐng)求,最終將請(qǐng)求的數(shù)據(jù)成功的顯示到了界面上。在此過(guò)程中,我們普遇到過(guò)如下錯(cuò)誤:

最終通過(guò)在測(cè)試文件的imports中加入了HttpClientModule將該錯(cuò)誤消除。本節(jié)我們將重點(diǎn)討論一下為什么引入了HttpClientModule后錯(cuò)誤就消息了。
在繼續(xù)學(xué)習(xí)以前,我們暫時(shí)移除測(cè)試文件`app.component.spec.ts`中對(duì)`HttpClientModule`的引入:
```typescript
imports: [
RouterTestingModule,
HttpClientModule ?
],
```
* ? 刪除該行
此時(shí)`ng t`將報(bào)如下錯(cuò)誤:

# 錯(cuò)在哪
解決問(wèn)題的前提是弄明白產(chǎn)生問(wèn)題的原因,找到問(wèn)題產(chǎn)生的原因的前提是找到出錯(cuò)代碼的位置。為了弄清楚到底是哪行代碼導(dǎo)致的異常,我們只保留該測(cè)試中的第一行代碼:
```typescript
it('組件初始化', () => {
const fixture = TestBed.createComponent(AppComponent);
// const app = fixture.componentInstance;
// expect(app).toBeTruthy();
//
// // 啟用angular的自動(dòng)變更檢測(cè)機(jī)制,自動(dòng)對(duì)V層中的數(shù)據(jù)進(jìn)行渲染
// fixture.autoDetectChanges();
});
```

異常依舊,此時(shí)我們大概可以確認(rèn)是第一行代碼出現(xiàn)了問(wèn)題。為了避免誤殺,我們?cè)趯⒌谝恍写a也注釋掉:
```typescript
it('組件初始化', () => {
// const fixture = TestBed.createComponent(AppComponent);
// const app = fixture.componentInstance;
// expect(app).toBeTruthy();
//
// 啟用angular的自動(dòng)變更檢測(cè)機(jī)制,自動(dòng)對(duì)V層中的數(shù)據(jù)進(jìn)行渲染
// fixture.autoDetectChanges();
});
```

錯(cuò)誤消失,則可以完全確認(rèn)是第一行代碼出了問(wèn)題。隨后移除剛剛第一行代碼的注釋后,我們繼續(xù)學(xué)習(xí)。
>[success] 當(dāng)發(fā)生一些預(yù)期以外的問(wèn)題時(shí),分塊注釋是個(gè)解決問(wèn)題的好辦法。
## 組件初始化
對(duì)組件進(jìn)行測(cè)試的前提是創(chuàng)建一個(gè)組件,而創(chuàng)建組件的過(guò)程中必然離不開(kāi)組件的實(shí)例化。為了更好的理解這一過(guò)程,我們對(duì)單元測(cè)試中的相關(guān)代碼并添加相應(yīng)注釋如下:
```typescript
it('組件初始化', () => {
// 創(chuàng)建一個(gè)夾具,該夾具中實(shí)例化了一個(gè)AppComponent對(duì)象
const fixture = TestBed.createComponent(AppComponent);
// 獲取夾具中的AppComponent對(duì)象
// const app = fixture.componentInstance;
// 預(yù)測(cè)上述過(guò)程沒(méi)有發(fā)生錯(cuò)誤,即成功獲取到了AppComponent對(duì)象
// expect(app).toBeTruthy();
// 啟用angular的自動(dòng)變更檢測(cè)機(jī)制,自動(dòng)對(duì)V層中的數(shù)據(jù)進(jìn)行渲染
// fixture.autoDetectChanges();
```
`TestBed.createComponent(AppComponent)`的流程圖大體如下:

通過(guò)該圖片可以得出結(jié)論:錯(cuò)誤產(chǎn)生的原因于Angular在能力初始化的過(guò)程中沒(méi)有設(shè)置提供`HttpClient`的能力。
而這個(gè)能力設(shè)置的對(duì)象,就是本節(jié)中我們將討論的**模塊**。
# 模塊
在Angular中**模塊**是可獨(dú)立運(yùn)行的最小單元,比如我們?cè)趕rc/app文件夾中的app.module.ts的作用就是在聲明一個(gè)**模塊**。
```bash
.
├── app-routing.module.ts
├── app.component.css
├── app.component.html
├── app.component.spec.ts
├── app.component.ts
└── app.module.ts ??
```
**組件**依賴(lài)于**模塊**,存在于**模塊**,組件若想成動(dòng)運(yùn)行,則必然是運(yùn)行于某個(gè)**模塊**之中。組件成功運(yùn)行的前提,是在**模塊**中被成功地實(shí)例化,**模塊**能夠成功實(shí)例化某個(gè)**組件**的前提是**模塊**擁有**組件**想要的一切。

## 測(cè)試模塊
在單元測(cè)試中,我們通過(guò)以下代碼創(chuàng)建一個(gè)測(cè)試模塊:
```typescript
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule,
],
declarations?: [
AppComponent?
],
}).compileComponents();
});
```
通過(guò)在?declarations中聲明?AppComponent來(lái)表示:AppComponent組件屬于當(dāng)前測(cè)試模塊。當(dāng)單元測(cè)試發(fā)生錯(cuò)誤時(shí),我們可以查看到當(dāng)前測(cè)試模塊的身影。

它的名稱(chēng)為DynamicTestModule,即動(dòng)態(tài)測(cè)試模塊,即為當(dāng)前AppComponent組件所在模塊。
一個(gè)**模塊**擁有什么樣的能力,除可以在`declarations`中聲明自身?yè)碛械哪芰σ酝?,還可以在`imports`引用外部的資源。比如當(dāng)前引入了`RouterTestingModule`,則當(dāng)前模塊擁有`RouterTestingModule`提供的相關(guān)能力。
```typescript
imports: [
RouterTestingModule,
],
```
由于我們要測(cè)試的`AppComponent`并不依賴(lài)于任何`RouterTestingModule`提供的能力,所以在此我們可以將其引入的代碼刪除。
```typescript
import {RouterTestingModule} from '@angular/router/testing'; ?
...
imports: [
RouterTestingModule, ?
],
```
## HttpClientModule
`declarations`與`imports`不同的是,前者僅能夠聲明`組件`等**模塊**的成員,而后者則是聲明的其它**模塊**。
與當(dāng)前**動(dòng)態(tài)測(cè)試模塊**相同,**HttpClientModule**也是一個(gè)模塊,該模塊下有一個(gè)**可用**的**HttpClient**成員,所以當(dāng)我們將`HttpClientModule`加入到`imports`中時(shí):
```typescript
imports: [
HttpClientModule
],
```
相當(dāng)于:

此時(shí)動(dòng)態(tài)測(cè)試模塊便擁有了兩個(gè)成員:自己聲明的`APP組件`以及引入的`HttpClientModule`模塊中的`HttpClient`。所以此時(shí)在執(zhí)行`TestBed.createComponent(AppComponent)`當(dāng)前模塊便擁有了創(chuàng)建`AppComponent`的先決條件。從而使得在動(dòng)態(tài)測(cè)試模塊中成功的創(chuàng)建了`AppComponent`
最后我們憂(yōu)愁單元測(cè)試相關(guān)注釋內(nèi)容:
```typescript
it('組件初始化', () => {
// 創(chuàng)建一個(gè)夾具,該夾具中實(shí)例化了一個(gè)AppComponent對(duì)象
const fixture = TestBed.createComponent(AppComponent);
// 獲取夾具中的AppComponent對(duì)象
const app = fixture.componentInstance;
// 預(yù)測(cè)上述過(guò)程沒(méi)有發(fā)生錯(cuò)誤,即成功獲取到了AppComponent對(duì)象
expect(app).toBeTruthy();
// 啟用angular的自動(dòng)變更檢測(cè)機(jī)制,自動(dòng)對(duì)V層中的數(shù)據(jù)進(jìn)行渲染
fixture.autoDetectChanges();
```
# 依賴(lài)注入
依賴(lài)注入(Dependency Injection)簡(jiǎn)稱(chēng)DI,是大部分框架支持的特性。好像已然成為了面試的必考題了一樣。在此,我們簡(jiǎn)單對(duì)此有個(gè)介紹。

簡(jiǎn)單來(lái)說(shuō):上圖中的????的過(guò)程稱(chēng)即被稱(chēng)為**依賴(lài)注入**。
因?yàn)????過(guò)程大體實(shí)現(xiàn)了:根據(jù)AppComponent聲明的**依賴(lài)**類(lèi)型`HttpClient`來(lái)將一個(gè)`HttpClient`實(shí)例**注入**給AppComponent。
更準(zhǔn)確的來(lái)講,**依賴(lài)**一詞源于UML類(lèi)圖中的類(lèi)與類(lèi)的一種關(guān)系,表示兩個(gè)類(lèi)之間存在的一種弱關(guān)系,是兩個(gè)類(lèi)耦合最小的一種。比如人類(lèi)當(dāng)前依賴(lài)于手機(jī)、依賴(lài)于網(wǎng)絡(luò),都是現(xiàn)實(shí)生活中的依賴(lài)的具體體現(xiàn)。
**注入**一詞就是給的意思,但這個(gè)給的操作是Angular主動(dòng)實(shí)施的,是自上而上的,所以稱(chēng)為**注入**更恰當(dāng)一些。
# 總結(jié)
讓我們簡(jiǎn)單總結(jié)下:
* 組件不能夠獨(dú)立存在,其必然屬于某個(gè)**模塊**
* 一個(gè)模塊的**能力**取決于兩個(gè)方面:
* 其`declarations`的聲明的成員
* 其`imports`中聲明模塊的**可用**成員
* 一個(gè)模塊的能力列表決定了其是否有創(chuàng)建某個(gè)組件的能力
# 資源列表
| 名稱(chēng) | 地址 |
|---- | ---- |
| Angular模塊簡(jiǎn)介 | [https://www.angular.cn/guide/ngmodules](https://www.angular.cn/guide/ngmodules) |
| Angular中的依賴(lài)注入 | [https://www.angular.cn/guide/dependency-injection](https://www.angular.cn/guide/dependency-injection) |
| 本節(jié)源碼 | [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 環(huán)境安裝
- 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 請(qǐng)求后臺(tái)數(shù)據(jù)
- 2.2.1 HttpClient
- 2.2.2 請(qǐng)求數(shù)據(jù)
- 2.2.3 模塊與依賴(lài)注入
- 2.2.4 異步與回調(diào)函數(shù)
- 2.2.5 集成測(cè)試
- 2.2.6 本章小節(jié)
- 2.3 新增教師
- 2.3.1 組件初始化
- 2.3.2 [(ngModel)]
- 2.3.3 對(duì)接后臺(tái)
- 2.3.4 路由
- 2.4 編輯教師
- 2.4.1 組件初始化
- 2.4.2 獲取路由參數(shù)
- 2.4.3 插值與模板表達(dá)式
- 2.4.4 初識(shí)泛型
- 2.4.5 更新教師
- 2.4.6 測(cè)試中的路由
- 2.5 刪除教師
- 2.6 收尾工作
- 2.6.1 RouterLink
- 2.6.2 fontawesome圖標(biāo)庫(kù)
- 2.6.3 firefox
- 2.7 總結(jié)
- 第三章 用戶(hù)登錄
- 3.1 初識(shí)單元測(cè)試
- 3.2 http概述
- 3.3 Basic access authentication
- 3.4 著陸組件
- 3.5 @Output
- 3.6 TypeScript 類(lèi)
- 3.7 瀏覽器緩存
- 3.8 總結(jié)
- 第四章 個(gè)人中心
- 4.1 原型
- 4.2 管道
- 4.3 對(duì)接后臺(tái)
- 4.4 x-auth-token認(rèn)證
- 4.5 攔截器
- 4.6 小結(jié)
- 第五章 系統(tǒng)菜單
- 5.1 延遲及測(cè)試
- 5.2 手動(dòng)創(chuàng)建組件
- 5.3 隱藏測(cè)試信息
- 5.4 規(guī)劃路由
- 5.5 定義菜單
- 5.6 注銷(xiāo)
- 5.7 小結(jié)
- 第六章 班級(jí)管理
- 6.1 新增班級(jí)
- 6.1.1 組件初始化
- 6.1.2 MockApi 新建班級(jí)
- 6.1.3 ApiInterceptor
- 6.1.4 數(shù)據(jù)驗(yàn)證
- 6.1.5 教師選擇列表
- 6.1.6 MockApi 教師列表
- 6.1.7 代碼重構(gòu)
- 6.1.8 小結(jié)
- 6.2 教師列表組件
- 6.2.1 初始化
- 6.2.2 響應(yīng)式表單
- 6.2.3 getTestScheduler()
- 6.2.4 應(yīng)用組件
- 6.2.5 小結(jié)
- 6.3 班級(jí)列表
- 6.3.1 原型設(shè)計(jì)
- 6.3.2 初始化分頁(yè)
- 6.3.3 MockApi
- 6.3.4 靜態(tài)分頁(yè)
- 6.3.5 動(dòng)態(tài)分頁(yè)
- 6.3.6 @Input()
- 6.4 編輯班級(jí)
- 6.4.1 測(cè)試模塊
- 6.4.2 響應(yīng)式表單驗(yàn)證
- 6.4.3 @Input()
- 6.4.4 FormGroup
- 6.4.5 自定義FormControl
- 6.4.6 代碼重構(gòu)
- 6.4.7 小結(jié)
- 6.5 刪除班級(jí)
- 6.6 集成測(cè)試
- 6.6.1 惰性加載
- 6.6.2 API攔截器
- 6.6.3 路由與跳轉(zhuǎn)
- 6.6.4 ngStyle
- 6.7 初識(shí)Service
- 6.7.1 catchError
- 6.7.2 單例服務(wù)
- 6.7.3 單元測(cè)試
- 6.8 小結(jié)
- 第七章 學(xué)生管理
- 7.1 班級(jí)列表組件
- 7.2 新增學(xué)生
- 7.2.1 exports
- 7.2.2 自定義驗(yàn)證器
- 7.2.3 異步驗(yàn)證器
- 7.2.4 再識(shí)DI
- 7.2.5 屬性型指令
- 7.2.6 完成功能
- 7.2.7 小結(jié)
- 7.3 單元測(cè)試進(jìn)階
- 7.4 學(xué)生列表
- 7.4.1 JSON對(duì)象與對(duì)象
- 7.4.2 單元測(cè)試
- 7.4.3 分頁(yè)模塊
- 7.4.4 子組件測(cè)試
- 7.4.5 重構(gòu)分頁(yè)
- 7.5 刪除學(xué)生
- 7.5.1 第三方dialog
- 7.5.2 批量刪除
- 7.5.3 面向?qū)ο?/a>
- 7.6 集成測(cè)試
- 7.7 編輯學(xué)生
- 7.7.1 初始化
- 7.7.2 自定義provider
- 7.7.3 更新學(xué)生
- 7.7.4 集成測(cè)試
- 7.7.5 可訂閱的路由參數(shù)
- 7.7.6 小結(jié)
- 7.8 總結(jié)
- 第八章 其它
- 8.1 打包構(gòu)建
- 8.2 發(fā)布部署
- 第九章 總結(jié)