在上一個小節中實現了學號、姓名在后臺的驗證,當學號、姓名不符合設定規則上,無法更新成功。本小節在此基礎上使用表單驗證的方式提升編輯功能的用戶使用感受。
# 介紹
表單驗證是個常用的功能,angular的官方文檔在基本原理一章中單獨將其列為一節 ---- [表單驗證](https://www.angular.cn/guide/form-validation),而且對模板驅動式表單及響應式表單的驗證方式單獨做了說明,編輯學生組件中使用的是`響應式表單`,本小節中將參考官方文檔完成姓名、學號的驗證。當姓名或學號不符合規則時給出友好的提示,且禁用保存按鈕。
驗證為前臺的新知識點,故采用集成測試的方式進行學習。在正式學習以前,仍然按原測試步驟:啟動數據庫、后臺、前臺、添加模擬數據的順序進行一些數據的準備工作。
## 驗證姓名
來到編輯學生界面:

### minLength
姓名的最小長度為2,則添加一個最小長度的驗證器:
src/app/student/edit/edit.component.ts
```javascript
ngOnInit() {
...
this.formGroup = new FormGroup({
name: new FormControl('', ?),
sno: new FormControl('')
});
}
```
* ? 在FormController的第二個參數中添加驗證器。
angular內置了常用的驗證器,此時添加最小長度驗證器`minLength`
src/app/student/edit/edit.component.ts
```javascript
import {FormControl, FormGroup, Validators} from '@angular/forms';
...
ngOnInit() {
...
this.formGroup = new FormGroup({
name: new FormControl('', Validators.minLength(2)?),
sno: new FormControl('')
});
}
```
* ? 最小長度驗證器,接收的參數為最小的長度值。`minLength(2)`表示最小的長度為2。
測試:
src/app/student/edit/edit.component.html
```
<label>姓名:<input name="name" formControlName="name"/></label>
<pre>{{formGroup.get('name').errors | json}}</pre> ? ?
<label>學號:<input name="sno" formControlName="sno"/></label>
```
* ? 若驗證失敗,將顯示在對應字段的error屬性上

觀察發現:
* ① 當name的值大于等于minLength設置值時,在字段name上的errors為null
* ② 當name有值且小于minLength的設置值時,在字段name上顯示對應的errors信息:minLength,該信息包括:設置的最小長度值、實際的長度值。
* ③ 當name無值時。minLength驗證器不生效。
根據以上信息,打造友好的驗證提醒如下:
src/app/student/edit/edit.component.html
```
<button type="submit">保存</button>
<div class="alert-warning" *ngIf="formGroup.get('name').errors"> ? ?
<p>輸入的姓名過短</p> ?
</div> ?
</form>
```
* ? 當name發生校驗錯誤時,顯示本警告框
測試:

### maxLength
maxLength及minLength的使用方法一致,除可以直接在FormControl上設置某個驗證器外,FormControl還支持批量設置驗證器。比如當前在name上再添加一個maxLength驗證器:
src/app/student/edit/edit.component.ts
```javascript
ngOnInit() {
...
this.formGroup = new FormGroup({
name: new FormControl('', [ // ?
Validators.minLength(2),
Validators.maxLength(10)]), // ?
sno: new FormControl('')
});
}
```
* ? FormControl的構造函數還支持第二個參數的類型為數組,在該數組中依次添加驗證器。
* ? 使用 maxLength來指定姓名的最大長度為10

有了多個驗證器后,就需要對提示信息進行簡單的處理了。
src/app/student/edit/edit.component.html
```html
<div class="alert-warning" *ngIf="formGroup.get('name').errors">
<p *ngIf="formGroup.get('name').errors?.minlength">輸入的姓名過短</p>
<p *ngIf="formGroup.get('name').errors?.maxlength">輸入的姓名過長</p>
</div>
</form>
```
* 加入`ngIf`來分別進行提示
### requried
加入最短最長提示后的確友好了很多,但當`name`值為空時`minLength`驗證器并沒有報錯。這便需要單獨對name為空進行提醒。requried的驗證方式有兩種,第一種是最簡單的在V層為`input`加入`requried`屬性;第二種是加入和`minlength`相似的驗證器。
src/app/student/edit/edit.component.html
```
<label>姓名:<input name="name" formControlName="name" required ?/></label>
```
* ? 這是最普通的html表單的寫法,該寫法被所有的瀏覽器所識別,當使用`required`標記的`input`為空時,將無法進行表單的提交

定制錯誤信息如下:
src/app/student/edit/edit.component.html
```html
<div class="alert-warning" *ngIf="formGroup.get('name').errors">
<p *ngIf="formGroup.get('name').errors?.minlength">輸入的姓名過短</p>
<p *ngIf="formGroup.get('name').errors?.maxlength">輸入的姓名過長</p>
<p *ngIf="formGroup.get('name').errors?.required">姓名不能為空</p> ?
</div>
```
測試:

除了直接為input添加`required`外,還可以在使用添加`required`驗證器的方式來達到驗證某個字段的目的。
src/app/student/edit/edit.component.ts
```javascript
this.formGroup = new FormGroup({
name: new FormControl('', [
Validators.minLength(2),
Validators.maxLength(10),
Validators.required]), ?
```
此時刪除V層`input`上的`required`也同樣達到驗證的效果。
src/app/student/edit/edit.component.html
```html
<label>姓名:<input name="name" formControlName="name"/></label>
```
測試效果與剛剛相同:

官方推薦的做法是即在C層中加入驗證器以明確的展示該自動是要進行`required`驗證的,又要在V層中添加required以使用瀏覽器默認的`required`驗證。所以最后讓我們恢復V層中剛剛刪除的`required`。
src/app/student/edit/edit.component.html
```html
<label>姓名:<input name="name" formControlName="name" required/></label>
```
## 驗證學號
學號的驗證規則是:長度必須為6位。angular并沒有固定長度驗證器,但可以使用`minLength`結合 `maxLength`來解決該問題:
src/app/student/edit/edit.component.ts
```javascript
this.formGroup = new FormGroup({
name: new FormControl('', [
Validators.minLength(2),
Validators.maxLength(10),
Validators.required]),
sno: new FormControl('', [
Validators.required, ?
Validators.maxLength(6), ?
Validators.minLength(6) ?
])
});
```
* 將最小最大長度均設置為6,則只有當長度為6時才不會發生錯誤。同時規定`required`,以解決當學號為空`minLength`驗證器失效的問題。
對應更新V層代碼:
src/app/student/edit/edit.component.html
```html
<label>學號:<input name="sno" formControlName="sno" required ? /></label>
...
<div class="alert-warning" *ngIf="formGroup.get('name').errors || formGroup.get('sno').errors"> ?
<p *ngIf="formGroup.get('name').errors?.minlength">輸入的姓名過短</p>
<p *ngIf="formGroup.get('name').errors?.maxlength">輸入的姓名過長</p>
<p *ngIf="formGroup.get('name').errors?.required">姓名不能為空</p>
<p *ngIf="formGroup.get('sno').errors">輸入的學號格式不正確</p> ?
</div>
```
* ? 使用`||`將`sno`字段報錯添加到顯示條件中
* ? 給出簡單的提示信息

# 驗證失敗禁用保存按鈕
在angular中可以使用在按鈕標簽上添加`[disabled]="調用的方法或值"`來禁用某個按鈕,比如:
src/app/student/edit/edit.component.html
```
<button type="submit" [disabled]="true">保存</button>
```

還可以將`[disabled]="true"`換成表達式,然后對應在C層中返回false達到同樣的效果:
src/app/student/edit/edit.component.html
```html
<button type="submit" [disabled]="disableSubmitButton()">保存</button>
```
src/app/student/edit/edit.component.ts
```
disableSubmitButton() {
return true;
}
```
效果相同:

接下來我們將formGroup傳入該表達式,然后在該表達式中對姓名及學號字段是否發生錯誤進行判斷,便可以達到當表單中的formControl驗證失敗時禁用保存按鈕的目的。
src/app/student/edit/edit.component.html
```html
<button type="submit" [disabled]="disableSubmitButton(fromGroup)?">保存</button>
```
* ? 將要驗證的formGroup由此傳入
src/app/student/edit/edit.component.ts
```javascript
/**
* 是否禁用保存按鈕
* 當姓名或學號任意字段驗證失敗時,均返回true
* @param formGroup 表單組
* @return true 禁用按鈕;false 啟用按鈕
*/
disableSubmitButton(formGroup: FormGroup): boolean {
if (formGroup.get('name').errors !== null) {
return true;
}
if (formGroup.get('sno').errors !== null) {
return true;
}
return false;
}
```
測試如下:

此外該方法還只可以簡寫為:
```javascript
disableSubmitButton(formGroup: FormGroup): boolean {
if (formGroup.get('name').errors !== null || formGroup.get('sno').errors !== null) {
return true;
}
return false;
}
```
再進一步簡寫為:
```javascript
disableSubmitButton(formGroup: FormGroup): boolean {
return formGroup.get('name').errors !== null || formGroup.get('sno').errors !== null;
}
```
# 參考文檔
| 名稱 | 鏈接 | 預計學習時長(分) |
| --- | --- | --- |
| 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step4.7.10](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step4.7.10) | - |
| FormControl | [https://angular.cn/api/forms/FormControl](https://angular.cn/api/forms/FormControl) | 5 |
| 表單驗證 | [https://www.angular.cn/guide/form-validation#form-validation](https://www.angular.cn/guide/form-validation#form-validation) | 15 |
- 序言
- 第一章: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
- 總結
- 開發規范
- 備用