本小節讓我們共同完成前后臺的對接。
在前面的章節中在進行班級列表組件對接的時候,我們直接在AppRouting中將klass路徑指定到班級列表組件;并在App模塊中引入了班級列表組件所在的模塊來防止引用錯誤。本節將使用惰性加載的方法將班級列表組件與App模塊進一步分離,從而達到惰性加載的目的(這可以大幅降低打包后項目的啟動時間)。
## 歷史代碼
在AppRoutingModule中,我們進行了如下聲明:
app-routing.module.ts
```js
{
path: 'klass',
component: IndexComponent
}
@NgModule({
imports: [RouterModule.forRoot(routes)], ①
exports: [RouterModule] ②
})
export class AppRoutingModule {
}
```
* ① 導入RouterModule,并使用routes對該模塊進行路由配置。
* ② 導出配置過的RouterModule,其它模塊若引用該模塊(AppRoutingModule),則將自動引用RouterModule。

所以當我們在AppModule中有如下代碼時:
app.module.ts
```
@NgModule({
declarations: [
AppComponent,
TeacherAddComponent,
TeacherEditComponent,
TeacherIndexComponent
],
imports: [
BrowserModule,
AppRoutingModule, ①
HttpClientModule,
FormsModule,
KlassModule ②
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}
```
* ① 引入AppRoutingModule,同時引入了由AppRoutingModule配置過的路由模塊RouterModule
* ① 由于在路由模塊中定義了`{path: 'klass', component: IndexComponent}`,所以:
* ② 聲明引入IndexComponent所在的KlassModule防止依賴錯誤
從思路上來講,上述使用方法沒有任何問題。但隨著在路由中聲明的組件越來越多,App模塊在啟動時將需要加載越來越多的信息來保證在路由中設定的component是可以被正確調用的,這將逐漸影響系統的啟動速度。**惰性加載**便是解決此問題的最佳實踐方法。
## 惰性加載
app-routing.module.ts
```
{
path: 'klass',
component: IndexComponent ?
loadChildren?: () => import('./klass/klass.module')?.then(mod? => mod.KlassModule?) ?
}
];
```
* ? 當路徑匹配`klass`時,加載子模塊。
* ? 子模塊位于`./klass/klass.module`
* ? 子模塊成功加載后,裝入mod對象(./klass/klass.module.ts)。
* ? 返回mod對象中的KlassModule給?
然后測試時有點意思的事情就發生了:

通過觀察我們發現,在啟動首頁的App模塊時,控制臺未報任何錯誤信息,而當我們點擊`班級管理`時,控制臺卻開始報錯了。
這是由于:
* 在沒有啟用**惰性加載**前,系統啟動時會嘗試加載所有路由對應的組件
* 而在啟用**惰性加載**后,App模塊在獲取到路由的loadChildren時,得到的是個`function`。當我們嘗試訪問`klass`路徑時,angular才會執行這個`function`會,嘗試得到程序下一步執行的模塊KlassModule,并進入Klass模塊繼續執行。
我們再來觀察一樣修改前后的代碼:
app-routing.module.ts
```
{
path: 'klass',
component: IndexComponent ?
loadChildren: () => import('./klass/klass.module').then(mod => mod.KlassModule) ?
}
];
```
進行類型轉換后:
```
{
path: 'klass',
component: 類 ---- angular立即嘗試獲取對應該類的對象
loadChildren: 函數 ---- angular立即將該函數載入(但不執行),僅當用戶實際訪問`klass`時才執行該函數。
}
];
```
由于`僅當用戶實際訪問`klass`時才執行該函數`,所以即使執行該函數以及在其以后的操作中會發生異常,也僅僅會在訪問`klass`路徑后發生。這也就是為什么當我們點擊`班級管理`后,控制臺才打印部分錯誤的原因。
## 排錯
讓我們用最原始最有效的方法來解決下這個錯誤:
```
core.js:6014 ERROR Error: Uncaught (in promise): Error: BrowserModule has already been loaded. If you need access to common directives such as NgIf and NgFor from a lazy loaded module, import CommonModule instead.
Error: BrowserModule has already been loaded. If you need access to common directives such as NgIf and NgFor from a lazy loaded module, import CommonModule instead.
```
上述提示譯成中文大概是說:`BrowserModule已經加載過了,如果你在惰性加載模塊需要訪問諸如NgIf或NgFor的指令,請使用CommonModule來替換BrowserModule`。
klass/klass.module.ts
```
@NgModule({
declarations: [IndexComponent, AddComponent],
imports: [
BrowserModule, ?
CommonModule, ?
FormsModule,
ReactiveFormsModule
]
})
```
#### 測試
此時,再次點擊班級管理錯誤消息,但未顯示任何信息:

這是由于我們未在班級模塊中定義任何路由的原因。
## 定義路由
找到klass/klass.module.ts,增加路由配置后代碼如下:
```
import {NgModule} from '@angular/core';
import {IndexComponent} from './index/index.component';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {AddComponent} from './add/add.component';
import {CommonModule} from '@angular/common';
import {RouterModule, Routes} from '@angular/router';
/*定義路由*/
const routes: Routes = [ ①
{
path: '',
component: IndexComponent
}, {
path: 'add',
component: AddComponent
}
];
/**
* 班級模塊
*/
@NgModule({
declarations: [IndexComponent, AddComponent],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
RouterModule.forChild(routes) ?
]
})
export class KlassModule {
}
```
* ① 定義路由
* ? 區別為forRoot,使用forChild定義惰性加載模塊路由
在App模塊中,我們使用的forRoot方法,這是由于App模塊是我們系統的根模塊,而forRoot即為:為根模塊定義路由。雖然在惰性加載模塊中使用了forChild來定義子路由,但并不必須這么做。在子模塊中,也是可以使用forRoot來建立根路由的,但通常情況下我們并不這么做。
#### 測試

# 參考文檔
| 名稱 | 鏈接 | 預計學習時長(分) |
| --- | --- | --- |
| 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.3.6](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.3.6) | - |
| 惰性加載路由配置 | [https://www.angular.cn/guide/router#lazy-loading-route-configuration](https://www.angular.cn/guide/router#lazy-loading-route-configuration) | 10 |
- 序言
- 第一章: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
- 總結
- 開發規范
- 備用