前言
想弱弱的问一句, 从事技术领域, 你有没有一两项技术点是不想涉及的? 我有, 超不喜欢做表单验证 ~ 刚开始打代码的时候, 做个表单验证, 我要写好多重复的 CSS 类, 好多重复的 JS 判断, 各种 IF AND ELSE. 不过随着技术慢慢成长, 而且现在有这么多的流行框架和组件, 也是可以欣慰一下的. 但是还是不想做表单验证, 这次做表单验证又研究了三天左右才有了起色, 因为现在想的不仅仅是完成了该功能, 更多在意的是逻辑强, 易维护, 语义化等.
回归正题, Angular2 的表单验证非常强大, 在这里我简单说一下, Angular2 的表单验证大致分为两类型:
这篇文章主要记录第二种方式 响应式表单方式
, 不过还是做个简单的对比, 先让大家了解一下.
简单认识 - 模板驱动表单方式
特点: 属性验证, 指令验证, 信息提示在页面表单内, 直接看段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <label for="name">Name</label> <input type="text" id="name" class="form-control" required minlength="4" maxlength="24" name="name" [(ngModel)]="person.name" #name="ngModel" > <div *ngIf="name.errors && (name.dirty || name.touched)" class="alert alert-danger"> <div [hidden]="!name.errors.required"> 昵称必填 </div> <div [hidden]="!name.errors.minlength"> 昵称不能少于 4 位字符 </div> <div [hidden]="!name.errors.maxlength"> 昵称不能大于 24 位字符 </div> </div>
|
简单认识 - 响应式表单方式
特点: 元素验证, 自定义指令验证, 信息配置在组件控制器中, 直接看段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| buildForm(): void { this.myForm = this.fb.group({ 'name': [this.person.name, [ Validators.required, Validators.minLength(4), Validators.maxLength(24) ] ], 'age': [this.person.age], 'mobile': [this.person.mobile, Validators.required] }); this.myForm.valueChanges .subscribe(data => this.onValueChanged(data)); this.onValueChanged(); }
|
以上就是两者的区别, 经实践并调研, 认为第二种方式的验证更为灵活一些, 可以增加自定义的指令, 正则表达式, 消息提示控制均可封装在一起, 好处多多, 你可以:
- 随时添加、修改和删除验证函数
- 在组件内动态操纵控制器模型
- 使用孤立单元测试来测试验证和控制器逻辑
深入了解 - 响应式表单方式
在制作响应式表单验证之前, 先介绍一个 @angular/forms
的依赖 - FormBuilder
.
FormBuilder
是一个名副其实的助手类, 帮助我们构建表单组 - ControlGroup
, 可以认为它是一个“工厂”对象.
1.先引入以下三个依赖, 我们之后都会用到
1
| import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
2.声明 FormGroup
, 实例化 FormBuilder
, 绑定我们表单元素并放至 fb.group()
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Person } from './person.class'; @Component({ selector: 'my-form', templateUrl: 'my-form.component.html', styleUrls: ['my-form.css'] }) export class myFormComponent { private myForm: FormGroup; private person = new Person(); constructor(private fb: FormBuilder) {} ngOnInit(): void { this.buildForm(); } private buildForm(): void { this.myForm = fb.group({ 'name': [this.person.name, [Validators.required]], 'mobile': [this.person.mobile, [ Validators.required, Validators.minLength(9) ]] }); } }
|
上述我们就在组件内, 简单绑定了 html 表单中的两个标签元素, name
, mobile
, 并为它们绑定了 Validators
提供的内置监测 required()
和 minLength()
, 即不能为空的和最少位数. 我们的响应式表单是如下这样子的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <form [formGroup]="myForm" (submit)="onSubmit(myForm.value)"> <div class="filed"> <label for="name">Name: </label> <input type="text" required [formControl]="myForm.controls['name']" placeholder="昵称"> </div> <div class="filed"> <label for="mobile">Mobile: </label> <input type="text" required [formControl]="myForm.controls['mobile']" placeholder="联系方式"> </div> <button type="submit" class="button">Submit</button> </form>
|
[formGroup]="myForm"
为绑定我们的表单;
[formControl]="myForm.controls['name']"
绑定我们的需要验证的元素, 这里还有另外一种方式, 也可写成:
1 2 3 4 5
| <input type="text" required [formControl]="myForm.controls['name']" placeholder="昵称"> <input type="text" required formControlName="name" placeholder="昵称">
|
3.下一步我们需要验证消息, 并提示在页面内, 先把我们页面显示信息的div写好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <form [formGroup]="myForm" (submit)="onSubmit(myForm.value)"> <div class="filed"> <label for="name">Name: </label> <input type="text" required formControlName="name" placeholder="昵称"> <div *ngIf="formErrors.name" class="alert alert-danger"> {{ formErrors.name }} </div> </div> <div class="filed"> <label for="mobile">mobile: </label> <input type="text" required formControlName="mobile" placeholder="联系方式"> <div *ngIf="formErrors.mobile" class="alert alert-danger"> {{ formErrors.mobile }} </div> </div> <button type="submit" class="button">Submit</button> </form>
|
上述代码我就在每个input元素之后, 添加了 formErrors.name
的绑定, formErrors
之后会在我们的组件内声明该变量, 用于储存我们的首要提示错误信息.
4.在组件中, 监测我们的表单变动, 并时时提示验证信息, 继续完善 step2
中的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| private buildForm(): void { this.myForm = fb.group({ 'name': [this.person.name, [Validators.required]], 'mobile': [this.person.mobile, [ Validators.required, Validators.minLength(9) ]] }); this.myForm.valueChanges.subscribe(data => _that.onValueChanged(data)); this.onValueChanged(); } private onValueChanged(data?: any) { let _that = this; if (!_that.myForm) { return; } const form = _that.myForm; for (const field in _that.formErrors) { _that.formErrors[field] = ''; const control = form.get(field); if (control && control.dirty && !control.valid) { const messages = _that.validationMessages[field]; for (const key in control.errors) { _that.formErrors[field] = messages[key]; } } } } private formErrors = { 'name': '', 'mobile': '' }; private validationMessages = { 'name': { 'required': '昵称是必填项' }, 'mobile': { 'required': '年龄是必填项', 'minlength': '最少为 9 为数字' } };
|
我们基本的响应式表单样式就这样结束了, html 表单的元素会根据, 订阅的 Form.valueChanges()
事件去填充 formErrors
, formErrors
会在页面显示我们最新的错误信息, 页面样式就随大家喜欢了.
就这样就结束了吗?
Validators
提供了 required
, minLengh
, maxLenght
好像满足不了我们高大上的表单安全验证, 所以我们要扩展我们的自定义指令, 来与 Validators
一起一个元素多个验证.
在这里我创建一个非常实用的多功能正则表达式自定义指令, 如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { ValidatorFn, FormControl } from '@angular/forms'; export function PatternValidator(pattern: RegExp): ValidatorFn { return (control: FormControl): { [s: string]: boolean } => { const reg = pattern instanceof RegExp ? pattern : new RegExp(pattern, 'i'); const res = !reg.test(control.value); return res ? {'pattern': true} : null; } }
|
上述需要注意的一点, 在 return 部分 {'pattern': true}
中的 pattern
这个key需要我们在组件内使用, 用来绑定我们的错误信息提示, 请往下看~
我们开使用该 PatternValidator
指令在我们的验证组中, 继续完善一小段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import { PatternValidator } from "../directives/form/v-pattern.directive"; export class myFormComponent { private buildForm(): void { this.myForm = fb.group({ 'name': [this.person.name, [ Validators.required, PatternValidator(/^[\S\s]{0,20}$/) ]], 'mobile': [this.person.mobile, [ Validators.required, PatternValidator(/^1[3|4|5|7|8]\d{9}$/) ]] }); this.myForm.valueChanges.subscribe(data => _that.onValueChanged(data)); this.onValueChanged(); } private validationMessages = { 'name': { 'required': '昵称是必填项', 'pattern': '1~20 位字符' }, 'mobile': { 'required': '年龄是必填项', 'pattern': '号码格式错误' } }; }
|
上述注意要引入我们的 PatternValidator
指令才能使用哦 ~
送你一记双向绑定, 肯定你需要
表单验证成功, 该保存或修改我们的数据了, 这么能快速拿到我们表单的全部数据呢, 这里使用双向绑定 ngModel
啦, 优化代码如下:
1 2 3
| <input type="text" required formControlName="name" [(ngModel)]="person.name" placeholder="昵称"> <input type="text" required formControlName="mobile" [(ngModel)]="person.mobile" placeholder="联系方式">
|
person
为我们在组件内声明的对象类:
1 2
| private person = new Person();
|
这个类看起来是这样子的:
1 2 3 4 5 6
| export class Person { public id: number; public name: string; public mobile: string; public active: number = 0; }
|
有了上述的 person
对象, 我们就可以随时拿到表单的数据了, 代码就行这样, 这就是我们建议一个模型类的好处:
1 2
| let data = this.person;
|
除了这个可以获取之外, 还有一种方式可以获取, 可还记得, 我们也绑定了表单:
1 2
| private myForm: FormGroup;
|
1 2
| let data = this.myForm.value;
|
但是需要注意的是, myForm.value
是拿的你声明在 formControlName
或者 fb.group
的元素组, 如果有注意命名方式的习惯, 请注意一下. (在这里我的Person对象都是以下划线链接, 因为要用作请求的数据, 页面的绑定声明则使用驼峰法, 所以我直接获取的是person的对象模型值).
另外 myForm 的值也可以做为参数传入 onSubmit()
方法中, 代码如下:
1
| <form [formGroup]="myForm" (submit)="onSubmit(myForm.value)"> </form>
|
Form 提交
我们想让表单元素都得到正确验证后, 才能让 submit
的 button 点亮, 所以我们再次优化一下我们的表单提交方式, 如下代码:
1 2 3 4
| <form [formGroup]="myForm"> <button type="submit" [class.disabled]="!myForm.valid" (submit)="onSubmit()" class="button">Submit</button> </form>
|
!myForm.valid
为监测我们的 Form 是否还有错误.
Wrapping Up
在这里我阐述了最轻量, 最灵活, 最整洁的一种验证方式的结合, 另外还有一些值, 也是可以使用的, 比如:
1.不声明对象模型, 只声明一个简单的变量并监测
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| private sth: any; private buildForm(): void { this.myForm = fb.group({ 'sth': ['', [Validators.required]] }); this.sth = this.myForm.controls['sth']; this.sth.valueChanges.subscribe( (value: string) => { console.log('sth changed to:', value); } ); }
|
2.不使用 FormErrors
绑定错误信息, 可以在页面随意绑定并显示
1 2 3 4 5 6
| <form [formGroup]="myForm"> <input type="text" [formControl]="myForm.controls['sth']"> <div *ngIf="!myForm.controls['sku'].valid" class="error message">Sth is invalid</div> <div *ngIf="myForm.controls['sku'].hasError('required')" class="error message">Sth is required</div> </form>
|
更多资料, 可参考:
Angular2官网 - 表单验证
Github - Angular2中的表单 这个是对 Angular2 还未印刷的英文书籍做的翻译, 仅表单部分
可能出现的小坑
1.有没有在 @NgModule
中引入 FormsModule
呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @NgModule({ imports: [ BrowserModule, FormsModule, ReactiveFormsModule ], declarations: [ AppComponent ], providers: [], bootstrap: [AppComponent] }) export class AppModule {}
|