在angular中可以簡單的這樣的認為:每個界面就一個組件。比如當前我們需要一個教師添加界面,那么就對應需要一個教師添加組件`TeacherAddComponent`。
# 創建組件
我們在`app`文件夾下,創建`TeacherAddComponent`的C層`teacher-add.component.ts`以及V層`teacher-add.component.html`并輸入以下內容:
app/teacher-add.component.ts
```js
import {Component, OnInit} from '@angular/core';
/**
* 教師添加組件
*/
@Component({
templateUrl: './teacher-add.component.html' ?
})
export class TeacherAddComponent implements OnInit { ?
ngOnInit(): void {
}
}
```
* ? 定義項目模板`teacher-add.component.html`
* ? 初始化組件
teacher-add.component.html
```html
<form id="teacherAddForm">
<div>
<label for="name">姓名:</label>
<input type="text" id="name" name="name"/>
</div>
<div>
<label for="username">用戶名:</label>
<input type="text" id="username" name="username">
</div>
<div>
<label for="email">郵箱:</label>
<input type="email" id="email" name="email">
</div>
<div>
<label>性別:</label>
<label> <input type="radio" name="sex" value="true"> 男</label>
<label> <input type="radio" name="sex" value="false"> 女</label>
</div>
<div>
<button>提交</button>
</div>
</form>
```
# 綁定路由
app/app-routing.module.ts
```js
import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {TeacherAddComponent} from './teacher-add.component';
const routes: Routes = [
{ ?
path: 'add', ?
component: TeacherAddComponent ?
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}
```
* ? routes數組中的每一項的類型均是對象`{}`。
* ? 該對象中的`path`屬性規定了訪問路徑。
* ? 該對象中的`component`屬性規定了對應的組件。
上面的過程我們還可以稱為:編排路由的索引,即訪問某些URL應該對應找到些組件。在英文中`map`譯成名詞是地圖,譯成動詞則可取編排索引之意,所以上述過程在英文中又叫做`mapping`。以`ing`結尾的代表正在進行的動作,所以我們剛剛在綁定路由的過程中,恰恰是在`mapping`。而`mapping`在計算機的專業術語中,又稱為`映射`。
# 測試

這個錯誤大概再說:TeacherAddComponent組件并不屬于任何模塊`或者`它雖然屬于某個模塊(潛臺詞)但該模塊并沒有并導入到當前模塊中。
之所以出現了該錯誤由于我同大家一樣,在編寫教程的時候,早就把教程中在前面講過的一張圖忘的一干二凈了。

> 我們如此聰明的大腦會自動忘掉一些我們認為并不重要的事情然后為更重要的事情來騰挪出記憶空間。我們成長的過程其實是一次次地抽象、匯總、總結的過程,把原本繁瑣的東西一點點的總結抽像壓縮到大腦恰恰可以掌握的程度。而最終的壓縮空間取決于我們在歷史上對此類知識的掌握以及我們主動去思考的時間。
由上圖可以看出:**組件是依賴于模塊而存在的**
## 組件添加到模塊
我們此時,再讀1.5.1小節,還會收獲:在`AppMoudle`的`@NgMoudle`注解的`declarations`選項是用來聲明該模塊下的組件的。
app.module.ts 變更前
```js
@NgModule({
declarations: [
AppComponent
],
```
app.module.ts 變更后
```js
import {TeacherAddComponent} from './teacher-add.component';
@NgModule({
declarations: [
AppComponent,
TeacherAddComponent
],
```
此時,我們訪問:[http://localhost:4200/add](http://localhost:4200/add)地址時,控制臺的錯誤消失。隨之而來的新問題是:為什么還在顯示`AppComponent`,而不是顯示`TeacherAddComponent`。這是由于:
* ① 系統定義了啟動組件為`AppComponent`,則在系統啟動時,永遠會啟動該組件。
* ② 路由定義僅代表定義了轉發規則,但并不代表立即啟動此規則。
* ③ 由啟動的的組件`AppComponent`來決定是否啟用路由規則。
## 在`AppComponent`中啟用路由
在`AppComponent`的V層的適當的位置添加代碼`<router-outlet></router-outlet>`,則表示:?加載路由組件(啟用路由)?將路由組件顯示的內容顯示在當前位置。
app.component.html
```
<router-outlet></router-outlet> ?
<table>
<tr>
<th>序號</th>
<th>姓名</th>
<th>用戶名</th>
<th>郵箱</th>
<th>性別</th>
<th>注冊時間</th>
<th>操作</th>
</tr>
<tr *ngFor="let teacher of teachers" >
<td>{{teacher.id}}</td>
<td>{{teacher.name}}</td>
<td>{{teacher.username}}</td>
<td>{{teacher.email}}</td>
<td>
<span *ngIf="teacher.sex">女</span>
<span *ngIf="!teacher.sex">男</span>
</td>
<td>{{teacher.createTime | date: 'yyyy-MM-dd'}}</td>
<td>刪除</td>
</tr>
</table>
```
* ? 放到教師列表前面
效果:

```html
<table>
<tr>
<th>序號</th>
<th>姓名</th>
<th>用戶名</th>
<th>郵箱</th>
<th>性別</th>
<th>注冊時間</th>
<th>操作</th>
</tr>
<tr *ngFor="let teacher of teachers" >
<td>{{teacher.id}}</td>
<td>{{teacher.name}}</td>
<td>{{teacher.username}}</td>
<td>{{teacher.email}}</td>
<td>
<span *ngIf="teacher.sex">女</span>
<span *ngIf="!teacher.sex">男</span>
</td>
<td>{{teacher.createTime | date: 'yyyy-MM-dd'}}</td>
<td>刪除</td>
</tr>
</table>
<router-outlet></router-outlet> ?
```
* ?放至教師列表后面

# 本節小測
嘗試再建立`TeacherEditComponent`組件,將其映射到`edit`地址上并測試。
## 上節答案
由于`bootstrap`屬性的接收值是一個數組,那么我認為是可以輸入多個啟動組件的。為了驗證自己想的是否正確,我直接將`TeacherAddComponent`也加入到啟動的列表中。
```js
import {TeacherAddComponent} from './teacher-add.component';
bootstrap: [AppComponent, TeacherAddComponent]
```
訪問[http://localhost:4200/](http://localhost:4200/)結果:
`TeacherAddComponent_Host.ngfactory.js? [sm]:1 ERROR Error: The selector "ng-component" did not match any elements`
> 結論一:不行,啟動組件只支持一個。
其實到這我們直接記往該結論就可以了,在很多項目中我們的確只使用一個`AppComponent`組件就夠了,這個結論雖然不見得是`絕對正確`的,但已經解決了我們當前碰到的問題,本著`夠用的就是最好的`原則我們已經圓滿的完成了任務。
*****
前面我們講過知識就是在不斷的證真與證違中積累及升華的。下面我進一步的展示一下該理論。
看報錯提示好像與啟動組件的個數無關,下面我繼承測試:在啟動屬性上只保留`TeacherAddComponent`,刪除原`AppComponent`組件,看是否正常啟動`TeacherAddComponent`。
```js
bootstrap: [TeacherAddComponent]
```
結果仍然是:
`TeacherAddComponent_Host.ngfactory.js? [sm]:1 ERROR Error: The selector "ng-component" did not match any elements`
此時,便發生了證違。即證明結論是`違`的,這將是對此知識點升華的開始。
> 結論二:結論一不一定正確。
*****
觀察報錯信息,說這個"ng-component" `selector` 沒有匹配上任何的元素。然后我們通過觀察`AppComponent`與`TeacherAddComponent`發現:`TeacherAddComponent`較`AppComponent`少了個`selector`屬性
app.component.ts
```js
@Component({
selector: 'app-root', ?
templateUrl: './app.component.html',
styleUrls: ['./app.component.sass']
})
/**
* 實現OnInit接口,該接口規定了ngOnInit方法。
* angular在組件準備完畢后,將自動調用ngOnInit方法
*/
export class AppComponent implements OnInit {
```
* ?此處有selector屬性
teacher-add.component.ts
```js
@Component({ ?
templateUrl: './teacher-add.component.html'
})
export class TeacherAddComponent implements OnInit {
ngOnInit(): void {
}
}
```
? 未添加selector
嘗試添加selector
teacher-add.component.ts
```js
@Component({
selector: 'app-teacher-add',
templateUrl: './teacher-add.component.html'
})
```
此時報錯信息變成了:
`TeacherAddComponent_Host.ngfactory.js? [sm]:1 ERROR Error: The selector "app-teacher-add" did not match any elements`
看來的確有影響,但卻沒有解決根本問題。
> 結論:是否配置selector對啟動成功與否無影響 。如果未給組件配置selector,則系統為其添加默認selector => `ng-component`。
*****
我們繼續瞎猜加驗證。剛剛報錯的`app-teacher-add`是我剛剛手動添加的,然后報錯了。那么以前使用`AppComponent`,其中的`selector`是`app-root`為什么不報錯呢?試試將兩個組件的`selector`換一下:
teacher-add.component.ts
```js
@Component({
selector: 'app-root',
templateUrl: './teacher-add.component.html'
})
export class TeacherAddComponent implements OnInit {
ngOnInit(): void {
}
}
```
app.component.ts
```js
@Component({
selector: 'app-teacher-add',
templateUrl: './app.component.html',
styleUrls: ['./app.component.sass']
})
/**
* 實現OnInit接口,該接口規定了ngOnInit方法。
* angular在組件準備完畢后,將自動調用ngOnInit方法
*/
export class AppComponent implements OnInit {
```
結果:

正常顯示了!
> 結論:可以變更啟動的組件,但該組件的`selector`必須是`app-root`
*****
我們繼續猜:該組件的`selector`必須是`app-root`,為什么不可以是其它呢?可以在哪指定了`app-root`這個值。全項目搜索下看看能不能找到這個指定的它的位置。

從搜索的結果上不難看出,原來在`index.html`中規定了這個`app-root`,打開該文件然后將其修改為:`app-teacher-add`呢?
index.html
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>WebApp</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-teacher-add></app-teacher-add>
</body>
</html>
```
結果:
`TeacherAddComponent_Host.ngfactory.js? [sm]:1 ERROR Error: The selector "app-root" did not match any elements`
我好像懂了:啟動時找`TeacherAddComponent`沒錯,`TeacherAddComponent`的selector為`app-root`(我們前面剛改了),系統啟動時在`index.html`中查找該selector`app-root`,如果沒找到則報 `The selector "app-root" did not match any elements`,即這個選擇器沒有匹配任何元素的錯誤。
> 結論三:啟用組件的selector必須在index.html中被使用。
*****
最后,我們再來徹底推翻原結論一,即啟動組件只能有一個。
我們先進行組件的恢復,將`TeacherAddComponent`的`selector`設置為`app-teacher-add`,將`AppComponent`的`selector` 設置為`app-root`。保持`index.html`中的`<app-teacher-add></app-teacher-add>`不變。
系統正常顯示教師添加界面。
然后將兩個組件都添加到啟動中:
```js
bootstrap: [TeacherAddComponent, AppComponent]
```
再將`app-root`也添加到`index.html`中。
index.html
```html
<body>
<app-teacher-add></app-teacher-add>
<app-root></app-root>
</body>
```
測試:

> 結論四:啟用組件可以為多個,但每個組的selector必須在index.html中被使用。
**總結:** 結論四并不見得正確,但它此時已經解決了我前面所有的問題。在當前來講,已經是最適用的合理的答案。
> 結論是否絕對正確并不重要,重要的是它是否適用于當前自己遇到的所有同類問題。
[源碼地址](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step2.3.1.0)
# 參考文檔
| 名稱 | 鏈接 | 預計學習時長(分) |
| --- | --- | --- |
| 入口組件 | [https://www.angular.cn/guide/entry-components](https://www.angular.cn/guide/entry-components) | 10 |
| 啟動過程 | [https://www.angular.cn/guide/bootstrapping](https://www.angular.cn/guide/bootstrapping) | 15 |
| 路由配置 | [https://www.angular.cn/guide/router#configuration](https://www.angular.cn/guide/router#configuration) | 5 |
| 路由出口 | [https://www.angular.cn/guide/router#router-outlet](https://www.angular.cn/guide/router#router-outlet) | 5 |
| 組件簡介 | [https://www.angular.cn/guide/architecture-components](https://www.angular.cn/guide/architecture-components) | 25 |
| 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step2.3.1](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step2.3.1) | - |
- 序言
- 第一章: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
- 總結
- 開發規范
- 備用