在上一个小节中实现了学号、姓名在后台的验证,当学号、姓名不符合设定规则上,无法更新成功。本小节在此基础上使用表单验证的方式提升编辑功能的用户使用感受。
# 介绍
表单验证是个常用的功能,angular的官方文档在基本原理一章中单独将其列为一节 ---- [表单验证](https://www.angular.cn/guide/form-validation),而且对模板驱动式表单及响应式表单的验证方式单独做了说明,编辑学生组件中使用的是`响应式表单`,本小节中将参考官方文档完成姓名、学号的验证。当姓名或学号不符合规则时给出友好的提示,且禁用保存按钮。
验证为前台的新知识点,故采用集成测试的方式进行学习。在正式学习以前,仍然按原测试步骤:启动数据库、后台、前台、添加模拟数据的顺序进行一些数据的准备工作。
## 验证姓名
来到编辑学生界面:
![](https://img.kancloud.cn/8b/5b/8b5bef5e52049aa6a0b81fd6678eb828_838x133.png)
### 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属性上
![](https://img.kancloud.cn/4a/a4/4aa467e6993eec5abf3491787b57f499_530x334.gif)
观察发现:
* ① 当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发生校验错误时,显示本警告框
测试:
![](https://img.kancloud.cn/96/1f/961f32d0bbdc667b3fa4a9c7635b6812_530x334.gif)
### 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
![](https://img.kancloud.cn/05/1d/051dfb8cff4dc6611af2dfdb6cc1bf38_524x286.png)
有了多个验证器后,就需要对提示信息进行简单的处理了。
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`为空时,将无法进行表单的提交
![](https://img.kancloud.cn/51/c5/51c58c425516eba9ec622efd5ec82afa_511x197.png)
定制错误信息如下:
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>
```
测试:
![](https://img.kancloud.cn/5a/ba/5abaa4112c5d43ba25ff25e876b94848_530x334.gif)
除了直接为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>
```
测试效果与刚刚相同:
![](https://img.kancloud.cn/5a/ba/5abaa4112c5d43ba25ff25e876b94848_530x334.gif)
官方推荐的做法是即在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`字段报错添加到显示条件中
* ➋ 给出简单的提示信息
![](https://img.kancloud.cn/35/a1/35a1be00f0682a859960ab7ce4299419_530x334.gif)
# 验证失败禁用保存按钮
在angular中可以使用在按钮标签上添加`[disabled]="调用的方法或值"`来禁用某个按钮,比如:
src/app/student/edit/edit.component.html
```
<button type="submit" [disabled]="true">保存</button>
```
![](https://img.kancloud.cn/56/b4/56b48d215fdd0689a3a074878a067f6e_811x105.png)
还可以将`[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;
}
```
效果相同:
![](https://img.kancloud.cn/56/b4/56b48d215fdd0689a3a074878a067f6e_811x105.png)
接下来我们将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;
}
```
测试如下:
![](https://img.kancloud.cn/3b/e5/3be5ca5ca48266b97df8349124ca51c8_1277x270.gif)
此外该方法还只可以简写为:
```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
- 总结
- 开发规范
- 备用