我們在前面的小節中成功地引用了`HttpClient`并使用其發起了數據請求,最終將請求的數據成功的顯示到了界面上。在此過程中,我們普遇到過如下錯誤:

最終通過在測試文件的imports中加入了HttpClientModule將該錯誤消除。本節我們將重點討論一下為什么引入了HttpClientModule后錯誤就消息了。
在繼續學習以前,我們暫時移除測試文件`app.component.spec.ts`中對`HttpClientModule`的引入:
```typescript
imports: [
RouterTestingModule,
HttpClientModule ?
],
```
* ? 刪除該行
此時`ng t`將報如下錯誤:

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

異常依舊,此時我們大概可以確認是第一行代碼出現了問題。為了避免誤殺,我們在將第一行代碼也注釋掉:
```typescript
it('組件初始化', () => {
// const fixture = TestBed.createComponent(AppComponent);
// const app = fixture.componentInstance;
// expect(app).toBeTruthy();
//
// 啟用angular的自動變更檢測機制,自動對V層中的數據進行渲染
// fixture.autoDetectChanges();
});
```

錯誤消失,則可以完全確認是第一行代碼出了問題。隨后移除剛剛第一行代碼的注釋后,我們繼續學習。
>[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)`的流程圖大體如下:

通過該圖片可以得出結論:錯誤產生的原因于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 ??
```
**組件**依賴于**模塊**,存在于**模塊**,組件若想成動運行,則必然是運行于某個**模塊**之中。組件成功運行的前提,是在**模塊**中被成功地實例化,**模塊**能夠成功實例化某個**組件**的前提是**模塊**擁有**組件**想要的一切。

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

它的名稱為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
],
```
相當于:

此時動態測試模塊便擁有了兩個成員:自己聲明的`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,是大部分框架支持的特性。好像已然成為了面試的必考題了一樣。在此,我們簡單對此有個介紹。

簡單來說:上圖中的????的過程稱即被稱為**依賴注入**。
因為????過程大體實現了:根據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 發布部署
- 第九章 總結