在前面的章節中,我們已經使用Angular成功的顯示了Hello World!。下面,我們一起探索一下src下的app文件夾。
```
├── app
│?? ├── app-routing.module.ts ?
│?? ├── app.component.html ? ?
│?? ├── app.component.sass ? ?
│?? ├── app.component.spec.ts ?
│?? ├── app.component.ts ? ?
│?? └── app.module.ts ?
```
此文件夾中有6個文件組成,共分成3個大部分。
* ? 路由文件,負責數據轉發。
* ? `app`組件。
* ? 組件視圖(V層)。
* ? 組件樣式(V層)。
* ? `app`組件對應的測試文件。
* ? 組件主體文件(C層)。
* ? `app`模塊,巧的是它的名字與組件重名(僅僅是名字相同而已,就像我們學校有條路起名為逸夫,大家稱它為逸夫路;我還學校有個圖書館,巧的是也叫逸夫,只不過它是逸夫樓一樣)。
他們間大體的關系如下圖示:

# 模塊
一個angular應用通常會有多個模塊(Module)組成。angular中的模塊共有9個配置部分。其中最常用的有4部分,筆者將其命名為:私有資源列表、公有資源列表、協作模塊列表以及擴展功能列表。

以`app`模塊為例認識下模塊的定義與基本組成:
app.module.ts
```typescript
import {BrowserModule} from '@angular/platform-browser'; ?
import {NgModule} from '@angular/core'; ?
import {AppRoutingModule} from './app-routing.module'; ?
import {AppComponent} from './app.component'; ?
@NgModule({ ?
declarations: [ ?
AppComponent ?
],
imports: [ ?
BrowserModule, ?
AppRoutingModule, ?
],
providers: [], ?
bootstrap: [AppComponent] ?
})
export class AppModule { ?
}
```
* ? 與php中的use, java中的import相同
* ? Angular的注解,將`AppModule`聲明為模塊
* ? 聲明當前模塊中的私有資源列表
* ? 將`app`組件加入到`app`模塊的私有資源列表
* ? 聲明模塊的協作模塊列表
* ? 將瀏覽器模塊加入到協作列表中,該模塊能夠提供的功能是:渲染html文件等。
* ? 將路由模塊加入到協作列表中,該模塊提供的功能由`app-routing.module.ts`中的`AppRouting`模塊所決定。
* ? 擴展功能列表
* ? 啟動組件,由于某些配置的原因,當程序運行時會自動啟動該組件。
* ? 定義類名并使用`export`聲明,表明該類可被`app.module.ts`以外的文件引用。
通過上面的解析可看到,使用`@NgModule`來聲明某類為angular應用中的一個模塊,使用`@NgModule`中配置相應的屬性來對此模塊進行配置。其中屬性`declarations`用于聲明私有資源列表,屬性`imports`用于聲明協作模塊列表,屬性`providers`用于聲明擴展功能列表。
所以模塊的組成也可以使用屬性關鍵字進行如下描述:

## import與imports
?處的import與?處的imports之間除了長的像以外,本質上并沒有其它的任何關聯。
import是typescript的語法,屬于編程語言層面,表示由某個文件中引入某個類、接口、方法至此文件。與其它語言的命名空間極為類型,稍有不同的是typescript并不需要在文件中使用package或namespace的字樣來規定命名空間,而是將文件的存儲位置直接做為了命名空間來使用。
而imports是屬于angular這個基于typescript開發而來的框架的`@NgModule`注解的一個屬性。它的含意是聲明angular中的模塊引入的其它的模塊。在angular應用中,功能的完成依賴模塊間的協同作業,而imports的作用便是來聲明某個模塊需要的協同者。
# 打開項目
找到前面安裝的WebStorm并運行,選擇open并找到項目文件:


稍等片刻,待WebStrom初始化完成。接著打開shell進入項目文件夾,并使用`ng serve --open`啟動項目并自動打開瀏覽器。
# 依賴注入
在Anguar中發起網絡請求需要借助`AppRoutingModule`中被聲明公有資源列表的`HttpClient`。`angular`中的組件可以構造函數中聲明自己所需要的依賴類型。angular在初始化該組件時,將嘗試在組件所在模塊中按構造函數中類型自動注入相關功能的實例。以`App`組件需要具有網絡請求功能的`HttpClient`為例:
AppComponent.ts
```typescript
import { Component } from '@angular/core';
import {HttpClient} from '@angular/common/http';?
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.sass']
})
export class AppComponent {
constructor(private httpClient: HttpClient) { ?
console.log(httpClient);
}
title = 'hello-world';
}
```
? 使用import由`@angular/common/http`中引入`HttpClient`到此文件。
? 構造函數中聲明依賴類型為HttpClient。
> 在后續的教程中將只給出文件名,當未對文件路徑做特殊說明時,表示該文件相對于`src/app/`文件夾。比如:`AppComponent.ts`實際為`src/app/AppComponent.ts`。
此時我們刷新頁面查看控制臺,將得到如下錯誤:

這是由于angular在嘗試注入HttpClient的過程中發生了錯誤。angular能夠成功完成HttpClient的注入,則`App`組件所在的`App`模塊需要滿足以下任一條件:
<hr>
???????????????????????????? **以下內容很重要,請牢記!** ????????????????????????????
1. 在當前模塊**私有資源列表**中能夠找到`HttpClient`。
2. 當前模塊的父模塊的**私有資源列表**中能夠找到`HttpClient`。
3. 在前模塊**協作模塊列表**中的任意模塊的**公有資源列表**中找到`HttpClient`
4. 在前模塊父模塊的**協作模塊列表**中的任意模塊的**公有資源列表**中找到`HttpClient`
???????????????????????????? **以上內容很重要,請牢記!** ????????????????????????????
<hr>
當前`App`模塊可如下可描述為:

App模塊在創建AppComponent時,由于在其私有資源中找到了AppComponent,近而進一步為其注入`HttpClient`。在注入`HttpClient`注入未成功進而發生錯誤。

這當然也在控制臺中報發下錯誤的原因:
```
RROR NullInjectorError: StaticInjectorError(AppModule)[AppComponent -> HttpClient]:
StaticInjectorError(Platform: core)[AppComponent -> HttpClient]:
NullInjectorError: No provider for HttpClient!
```
> 如果某種錯誤是第一次出現,出現后你完全沒有任何解決它的思路,此時你絕對不應該去傻傻的看代碼是不是哪有問題。你的正確操作應該是:翻譯!
```
嚴重錯誤 空注入器錯誤:靜態注入器錯誤(AppMoule上發生的[在向AppComponent注入HttpClient時]:
靜態注入器錯誤(在Platform:core上發生的)[在向AppComponent注入HttpClient時]:
空注入器錯誤: 沒有HttpCilent的提供者
```
出錯的原因找到了,解決問題的方法也就隨著有了。

>[success] 在angular中,任何模塊都屬于Root模塊的子模塊。
對應代碼如下:
app.module.ts
```typescript
import {NgModule} from '@angular/core';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {HttpClientModule} from '@angular/common/http'; ?
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule ?
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}
```
* ? 由@angular/common/http中引入HttpClientModule至此文件
* ? 在協作模塊列表中加入可以提供`HttpClient`的`HttpClientModule`
# 測試
打開控制臺(F12),查看測試消息如下:

控制臺中成功的打印了`HttpClient`對象的信息同時未發生任何錯誤,注入成功。下個小節中展示如何使用`HttpClient`發起網絡請求。
## 生活與計算機
計算機來源于生活,現實生活中大概會有這樣的場景:技術部門組織春游需要一輛巴士,向公司統一協調部門打電話告知其需求,即:我們需要一輛巴士,此外還需要配備一名司機。

上面的需求翻譯成代碼語言如下:
```
export class Play {
constructor(private bus: Bus, private driver: Driver)
}
}
```
其實此時我們并不關心統一協調部門為我們提供的巴士是公司自有車輛還是其由外面租賃的,也不關心巴士的顏色是紅的還是綠的,當然也不關心司機是男是女。我們關心是功能,即巴士有運載人員的功能,司機有駕駛巴士的功能。
統一協調部門得到這個需要以后,去調配巴士和司機,比如此時公司的資源是這樣的:

統一調度部門發現能夠滿足我們的需求,于是將巴士及能駕駛巴士的司機指派給我們。

要知道并不是所有的公司都有擁有自有用車的,那么當這類公司中的內部部門向統一協調部分發送用車需求時,統一協調部門該怎么辦呢?相信你早有了答案 ---- 找第三租車公司呀!
按上面的思路,假設這個公司的汽車服務需要借肋于租車公司、零食需要借助于超市、藥品需要借助于藥店,那么就會出現如下情況:

當其它部門提出`汽車+司機+藥品+零食`、`汽車+司機+藥品`、`藥品+零食`需求時,統一協調部門都是可以做到的。但如果有部門提出`飛機+零食`的需求時,統一協調部門就做不到了,此時統一協調部門就會報異常,因為他指不到`飛機`的供應商來滿足其它部門的需求。
>[success] 在計算機的世界里,我們把這種借助統一協調部門來自動提供指定需求的方法稱為**依賴注入 ---- Dependency injection**,也就是你在面試時可能會被問到的`DI`。
## 計算機與生活
在我們剛剛接觸的Angular的世界里,`app module(模塊)`就是這個公司下的統一協調部門,負責該公司一切資源的調配;`app component組件`則是提出需求的其它部門,提出需求的方法是通過構造函數來聲明。
所以我們前面剛剛添加過的app組件:
AppComponet.ts
```
constructor(private httpClient: HttpClient) { ?
console.log(httpClient);
}
```
? 找自己所在的module要`HttpClient`。
前面我們分析`AppMoudle`時講過了:`AppComponent`屬于`AppMoudle`。所以這里的`HttpClient`應該由`AppMoudle`來提供,但此時`AppMoudle`還沒有提供`HttpClient`的能力,所以我們打開控制臺后,就出現了:
有了以上的提示再加上我們前面對DI知識的學習,解決這個問題也就不困難了。
# 本節小測
? 控制臺報錯為:
```
RROR NullInjectorError: StaticInjectorError(AppModule)[AppComponent -> HttpClient]:
StaticInjectorError(Platform: core)[AppComponent -> HttpClient]:
NullInjectorError: No provider for HttpClient!
```
報錯內容一直在說`HttpClient`錯誤,而在`AppModule`中卻這樣聲明:
```
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule ?
],
```
問題:為什么提示說缺少HttpClient,但我們卻在此imports了`HttpClientModule`呢?
- 序言
- 第一章:Hello World
- 第一節:Angular準備工作
- 1 Node.js
- 2 npm
- 3 WebStorm
- 第二節:Hello Angular
- 第三節:Spring Boot準備工作
- 1 JDK
- 2 MAVEN
- 3 IDEA
- 第四節:Hello Spring Boot
- 1 Spring Initializr
- 2 Hello Spring Boot!
- 3 maven國內源配置
- 4 package與import
- 第五節:Hello Spring Boot + Angular
- 1 依賴注入【前】
- 2 HttpClient獲取數據【前】
- 3 數據綁定【前】
- 4 回調函數【選學】
- 第二章 教師管理
- 第一節 數據庫初始化
- 第二節 CRUD之R查數據
- 1 原型初始化【前】
- 2 連接數據庫【后】
- 3 使用JDBC讀取數據【后】
- 4 前后臺對接
- 5 ng-if【前】
- 6 日期管道【前】
- 第三節 CRUD之C增數據
- 1 新建組件并映射路由【前】
- 2 模板驅動表單【前】
- 3 httpClient post請求【前】
- 4 保存數據【后】
- 5 組件間調用【前】
- 第四節 CRUD之U改數據
- 1 路由參數【前】
- 2 請求映射【后】
- 3 前后臺對接【前】
- 4 更新數據【前】
- 5 更新某個教師【后】
- 6 路由器鏈接【前】
- 7 觀察者模式【前】
- 第五節 CRUD之D刪數據
- 1 綁定到用戶輸入事件【前】
- 2 刪除某個教師【后】
- 第六節 代碼重構
- 1 文件夾化【前】
- 2 優化交互體驗【前】
- 3 相對與絕對地址【前】
- 第三章 班級管理
- 第一節 JPA初始化數據表
- 第二節 班級列表
- 1 新建模塊【前】
- 2 初識單元測試【前】
- 3 初始化原型【前】
- 4 面向對象【前】
- 5 測試HTTP請求【前】
- 6 測試INPUT【前】
- 7 測試BUTTON【前】
- 8 @RequestParam【后】
- 9 Repository【后】
- 10 前后臺對接【前】
- 第三節 新增班級
- 1 初始化【前】
- 2 響應式表單【前】
- 3 測試POST請求【前】
- 4 JPA插入數據【后】
- 5 單元測試【后】
- 6 惰性加載【前】
- 7 對接【前】
- 第四節 編輯班級
- 1 FormGroup【前】
- 2 x、[x]、{{x}}與(x)【前】
- 3 模擬路由服務【前】
- 4 測試間諜spy【前】
- 5 使用JPA更新數據【后】
- 6 分層開發【后】
- 7 前后臺對接
- 8 深入imports【前】
- 9 深入exports【前】
- 第五節 選擇教師組件
- 1 初始化【前】
- 2 動態數據綁定【前】
- 3 初識泛型
- 4 @Output()【前】
- 5 @Input()【前】
- 6 再識單元測試【前】
- 7 其它問題
- 第六節 刪除班級
- 1 TDD【前】
- 2 TDD【后】
- 3 前后臺對接
- 第四章 學生管理
- 第一節 引入Bootstrap【前】
- 第二節 NAV導航組件【前】
- 1 初始化
- 2 Bootstrap格式化
- 3 RouterLinkActive
- 第三節 footer組件【前】
- 第四節 歡迎界面【前】
- 第五節 新增學生
- 1 初始化【前】
- 2 選擇班級組件【前】
- 3 復用選擇組件【前】
- 4 完善功能【前】
- 5 MVC【前】
- 6 非NULL校驗【后】
- 7 唯一性校驗【后】
- 8 @PrePersist【后】
- 9 CM層開發【后】
- 10 集成測試
- 第六節 學生列表
- 1 分頁【后】
- 2 HashMap與LinkedHashMap
- 3 初識綜合查詢【后】
- 4 綜合查詢進階【后】
- 5 小試綜合查詢【后】
- 6 初始化【前】
- 7 M層【前】
- 8 單元測試與分頁【前】
- 9 單選與多選【前】
- 10 集成測試
- 第七節 編輯學生
- 1 初始化【前】
- 2 嵌套組件測試【前】
- 3 功能開發【前】
- 4 JsonPath【后】
- 5 spyOn【后】
- 6 集成測試
- 7 @Input 異步傳值【前】
- 8 值傳遞與引入傳遞
- 9 @PreUpdate【后】
- 10 表單驗證【前】
- 第八節 刪除學生
- 1 CSS選擇器【前】
- 2 confirm【前】
- 3 功能開發與測試【后】
- 4 集成測試
- 5 定制提示框【前】
- 6 引入圖標庫【前】
- 第九節 集成測試
- 第五章 登錄與注銷
- 第一節:普通登錄
- 1 原型【前】
- 2 功能設計【前】
- 3 功能設計【后】
- 4 應用登錄組件【前】
- 5 注銷【前】
- 6 保留登錄狀態【前】
- 第二節:你是誰
- 1 過濾器【后】
- 2 令牌機制【后】
- 3 裝飾器模式【后】
- 4 攔截器【前】
- 5 RxJS操作符【前】
- 6 用戶登錄與注銷【后】
- 7 個人中心【前】
- 8 攔截器【后】
- 9 集成測試
- 10 單例模式
- 第六章 課程管理
- 第一節 新增課程
- 1 初始化【前】
- 2 嵌套組件測試【前】
- 3 async管道【前】
- 4 優雅的測試【前】
- 5 功能開發【前】
- 6 實體監聽器【后】
- 7 @ManyToMany【后】
- 8 集成測試【前】
- 9 異步驗證器【前】
- 10 詳解CORS【前】
- 第二節 課程列表
- 第三節 果斷
- 1 初始化【前】
- 2 分頁組件【前】
- 2 分頁組件【前】
- 3 綜合查詢【前】
- 4 綜合查詢【后】
- 4 綜合查詢【后】
- 第節 班級列表
- 第節 教師列表
- 第節 編輯課程
- TODO返回機制【前】
- 4 彈出框組件【前】
- 5 多路由出口【前】
- 第節 刪除課程
- 第七章 權限管理
- 第一節 AOP
- 總結
- 開發規范
- 備用