# 初始化組件
是的,所有的功能在開發前都離不開初始化的工作。只有當組件被初始化完成后,整個功能才能抽象變更為具體。團隊內部以及我們與甲方的交流也因此會加的順暢,這保證了我們將開發一個與甲方預期一致的系統。
## 新建模塊
我們在第二章曾經對模塊進行了如下總結:

可見模塊與模塊間是通過`imports`來配合完成工作的。
在教師管理一章中,我們使用了Angular初始化的自帶模塊`AppModule`,本節我們初始化一個單獨用于班級管理的模塊`ClazzModule`,然后在此模塊中添加教師管理中的新增、列表、編輯等組件。這樣的做當前的好處是可以使原本越來越亂的目錄結構變得稍微的清爽一些。
我們使用shell來到src/app文件夾,執行`ng g module clazz`命令來快速的創建一個模塊:
```bash
panjie@panjies-Mac-Pro app % pwd
/Users/panjie/github/mengyunzhi/angular11-guild/first-app/src/app
panjie@panjies-Mac-Pro app % ng g module clazz
CREATE src/app/clazz/clazz.module.ts (191 bytes)
```
該命令將自動為我們在app文件夾下創建一個新的文件夾clazz,并在該文件夾下自動創建一個`clazz.module.ts`文件。
```bash
panjie@panjies-Mac-Pro app % tree clazz
clazz
└── clazz.module.ts
```
文件內容如下:
```typescript
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
@NgModule({
declarations: [],
imports: [
CommonModule
]
})
export class ClazzModule {
}
```
如上所示,類`ClazzModule`是使用了注解`@NgModule` ,因此被Angular識別為`模塊`。
## 新建組件
接著使用shell進入src/app/clazz文件夾,執行創建組件命令:
```bash
panjie@panjies-Mac-Pro clazz % pwd
/Users/panjie/github/mengyunzhi/angular11-guild/first-app/src/app/clazz
panjie@panjies-Mac-Pro clazz % ng g c add
CREATE src/app/clazz/add/add.component.css (0 bytes)
CREATE src/app/clazz/add/add.component.html (18 bytes)
CREATE src/app/clazz/add/add.component.spec.ts (605 bytes)
CREATE src/app/clazz/add/add.component.ts (263 bytes)
UPDATE src/app/clazz/clazz.module.ts (250 bytes)
```
**注意**:必須確保shell所在文件夾位置為src/app/clazz。
Angular聰明的在當前文件夾創建了班級add組件,并將其聲明到了clazz.module.ts中,以表明班級add組件為班級模塊的一部分。
### V層
模板代碼如下:
```html
b/first-app/src/app/clazz/add/add.component.html
<form class="container-sm">
<div class="mb-3 row">
<label for="name" class="col-sm-2 col-form-label">名稱</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="name" id="name">
</div>
</div>
<div class="mb-3 row">
<label for="teacher" class="col-sm-2 col-form-label">班主任</label>
<div class="col-sm-10">
<select id="teacher" class="form-control" >
<option>張三</option>
<option>李四</option>
</select>
</div>
</div>
<div class="mb-3 row">
<div class="col-sm-10 offset-2">
<button class="btn btn-primary">保存</button>
</div>
</div>
</form>
```
使用`ng t`后啟用單元測試并查看效果如下:

### CV交互
select選擇框如何做我們還不太清楚,但部分的功能我們還是學習過的,讓我們簡單來實現這些功能。在CV的交互中,至于先寫哪個后寫哪個完全看自己的習慣,喜歡先寫哪個都可以,比如我在這比較喜歡先寫V層:
```html
+++ b/first-app/src/app/clazz/add/add.component.html
@@ -1,14 +1,15 @@
-<form class="container-sm">
+<pre>{{clazz | json}}</pre> ??
+<form class="container-sm" (ngSubmit)="onSubmit()">
<div class="mb-3 row">
<label for="name" class="col-sm-2 col-form-label">名稱</label>
<div class="col-sm-10">
- <input type="text" class="form-control" name="name" id="name">
+ <input type="text" class="form-control" [(ngModel)]="clazz.name" name="name" id="name">
</div>
</div>
<div class="mb-3 row">
<label for="teacher" class="col-sm-2 col-form-label">班主任</label>
<div class="col-sm-10">
- <select id="teacher" class="form-control" >
+ <select id="teacher" class="form-control">
<option>張三</option>
<option>李四</option>
</select>
```
在這增加一個`<pre>`標簽會有意想不到的收獲。
再寫C層:
```typescript
+++ b/first-app/src/app/clazz/add/add.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnInit } from '@angular/core';
+import {Component, OnInit} from '@angular/core';
@Component({
selector: 'app-add',
@@ -6,10 +6,17 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./add.component.css']
})
export class AddComponent implements OnInit {
+ clazz = {
+ name: ''
+ };
- constructor() { }
+ constructor() {
+ }
ngOnInit(): void {
}
+ onSubmit(): void {
+ console.log('submit');
+ }
}
```
這時候聰明的WebStorm將會在V層給我們一些錯誤的提示:


錯誤的意思都是在說沒有任何的`directive`來支持我們剛剛寫的代碼,比如:`(ngSubmit)`以及`[(ngModel)]`。事實也是這樣,由于`(ngSubmit)`以及`[(ngModel)]`并不存在于html語言中,所以要想使用其生效,則需要有一個叫做`directive`的東西來支持這樣功能。而前我們提及過的`FromModule`便是能提供支持`(ngSubmit)`以及`[(ngModel)]`的模塊。
模塊與模塊的交流位于模塊的`imports`,為些我們將`FormModule`添加到當前組件所在的ClazzModule中:
```typescript
--- a/first-app/src/app/clazz/clazz.module.ts
+++ b/first-app/src/app/clazz/clazz.module.ts
@@ -1,12 +1,14 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import { AddComponent } from './add/add.component';
+import {FormsModule} from '@angular/forms';
@NgModule({
declarations: [AddComponent],
imports: [
- CommonModule
+ CommonModule,
+ FormsModule
]
})
export class ClazzModule {
```
此時V層的錯誤消失。其實此時`ng t`所依賴的動態測試模塊也面臨著這個錯誤,只是WebStorme還沒有智能到能檢測出該錯誤,所以我們手動添加一下:
```typescript
--- a/first-app/src/app/clazz/clazz.module.ts
+++ b/first-app/src/app/clazz/clazz.module.ts
@@ -1,12 +1,14 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import { AddComponent } from './add/add.component';
+import {FormsModule} from '@angular/forms';
@NgModule({
declarations: [AddComponent],
imports: [
- CommonModule
+ CommonModule,
+ FormsModule
]
})
export class ClazzModule {
```
至此大多數的初始化工作,就完成了。我們最后在單元測試中加入**自動檢測變更**的代碼后,當前的組件便會在單元測試中動起來了。
## Select
當領導給我們分配一些新的任務時,**我不會**三個字永遠不要輕易的就說出口。在現有水平的基礎上,合理的利用網上的學習資源,絕對能解決大多數的問題。即使最后沒有得到解決的答案,基于當前自己找到的資源去和領導溝通也遠勝過**我不會**三個字。
雖然我們還沒有學習過如何使用Select,但我相信你當前一定有能力解決這個問題。Select使用方式與普通的input非常的相似:在元素上增加`name`屬性并直接應用`([ngModel])`:
```html
+++ b/first-app/src/app/clazz/add/add.component.html
@@ -9,9 +9,11 @@
<div class="mb-3 row">
<label for="teacher" class="col-sm-2 col-form-label">班主任</label>
<div class="col-sm-10">
- <select id="teacher" class="form-control">
- <option>張三</option>
- <option>李四</option>
+ <select id="teacher" class="form-control"
+ name="teacher"
+ [(ngModel)]="clazz.teacherId">
+ <option [ngValue]="1">張三</option> ??
+ <option [ngValue]="2">李四</option>
</select>
</div>
</div>
```
**注意**:這里使用`[ngValue]`而非`value`,在angular中`[xx]="yyy"`中的yyy表示typescript中的變量或值,我們在這里需要的值是number類型的`1`, `2`,而非字符串類型的`'1'`,`'2'`。如果你學過一些數據庫的相關知道,一定知道數字1,2,3與字符1,2,3的區別。如果使用的是value而非`[ngValue]`,則將得到字符串`1`,`2`。
接著在C層中同步為clazz添加一個teacherId字段:
```typescript
+++ b/first-app/src/app/clazz/add/add.component.ts
@@ -7,7 +7,8 @@ import {Component, OnInit} from '@angular/core';
})
export class AddComponent implements OnInit {
clazz = {
- name: ''
+ name: '',
+ teacherId: null as unknown as number
};
constructor() {
```
測試如下:

如果我們在`option`元素上,使用了`value`而非`[ngValue]`,則將得到字符類型的`'1'`:

而這并不是我們想要的。
## 本節作業
新增班級的API為:
```bash
POST /clazz
```
| **類型Type** | **名稱Name** | **描述Description** | **類型Schema** |
| :----------- | :----------- | :------------------ | :----------------------------------------------------------- |
| Body | clazz | 班級 | `{name: string, teacher: {id: number}}` |
| Response | | 響應 | `{id: number, name: string, createTime: number, teacher: {id: number, name: string}}` |
請求Body`{name: string, teacher: {id: number}}`中的teacher屬性的類型是一個對象,該對象有一個id屬性,類型為number。在響應信息中,給出了保存后的班級ID,以及班級對應的teacher對象基本信息。
1. 請嘗試在`ng t`的情況下完成它。
2. 以下信息可能會給問題的解決帶來一些思路:
1. `ng t`下報401的錯誤原因為何?
2. 如何在新建班級前完成用戶的登錄?
| 名稱 | 鏈接 |
| ---------------------- | ------------------------------------------------------------ |
| 本節源碼(包含本節答案) | [https://github.com/mengyunzhi/angular11-guild/archive/step6.1.1.zip](https://github.com/mengyunzhi/angular11-guild/archive/step6.1.1.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 發布部署
- 第九章 總結