Angular2 模板驱动表单校验
模板驱动表单,指的是通过html5标准校验的表单,优势在于使用起来简单,但要动态修改验证器、操纵控制器模型不是很方便。
Angular2对表单处理做了一系列封装(模板驱动表单以及响应式表单):
-
数据绑定
这个自然不用说,使用ngModel可以双向绑定到组件里的对象字段。
-
控件状态检测
Angular会自动根据控件状态加上相应的class,如果我们需要编辑input标签在不同状态下的样式,只需要在css里写相应的类就可以了。
状态 true时的css类 false时的css类 控件是否被访问过 ng-touched ng-untouched 控件值是否已经变化 ng-dirty ng-pristine 控件值是否有效 ng-valid ng-invalid -
表单校验
模板驱动表单支持html5标准属性校验:
- required:必填
- minlength:最小长度
- maxlength:最大长度
- pattern:正则表达式校验
另外支持自定义Validator.
响应式表单内置了上面四种Validator,也可以自己扩展。
模板驱动表单相关指令封装在FormsModule模块中,app.module.ts里需要先导入:
import {FormsModule} from '@angular/forms';
@NgModule({
declarations: [
AppComponent,
...
],
exports:[AppComponent],
imports: [
BrowserModule,
FormsModule,
HttpModule,
...
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
FormsModule模块包含NgModule、NgModuleGroup、NgForm和InternalFormsSharedModule模块中包含的指令。
- NgForm 标记一个表单
- NgModelGroup 字段分组
- NgModel 字段
InternalFormsSharedModule是Angular内部模块,FormsModule和ReactiveFormsModule都引用了它,所以可以不用显式引入,直接使用。
下面的例子演示了一个模板驱动表单,包括表单校验、字段分组、控件状态、数据绑定,以及自定义校验器。自定义校验器的功能是校验第二个密码是否与第一个密码相同。
自定义校验器:
repeat-password.directive.ts:
import {Directive, Input, OnChanges, SimpleChanges} from '@angular/core'; import {NG_VALIDATORS, FormControl, Validator, AbstractControl, ValidatorFn, NgModel} from "@angular/forms"; /** * 自定义指令,用于检验input标签的值是否跟指定input的值标签相同 */ @Directive({ selector: '[repeatPassword]', providers: [{provide: NG_VALIDATORS, useExisting: RepeatPasswordDirective, multi: true}] }) export class RepeatPasswordDirective implements Validator,OnChanges{ /** * 校验方法 * @param c * @returns {{[p: string]: any}} */ validate(c: AbstractControl): {[p: string]: any} { return verifyPassword(c,this.repeatPassword.control); } ngOnChanges(changes: SimpleChanges): void { this.repeatPassword=changes['repeatPassword'].currentValue; } /** * 通过属性传入另一个input标签的model * 名称与选择器一致,就不需要在使用的时候加额外的属性传入 */ @Input() repeatPassword:NgModel; constructor() { } } /** * 导出校验方法,供响应式表单使用 * @param password1Controller * @returns {(currentControl:AbstractControl)=>{[p: string]: any}} */ export function repeatPassword(password1Controller:FormControl):ValidatorFn { return (currentControl: AbstractControl): {[key: string]: any} => { return verifyPassword(currentControl,password1Controller); }; } function verifyPassword(currentControl: AbstractControl,password1Controller:FormControl):{[key: string]: any} { if(!password1Controller.valid) { console.log("密码1无效"); return {password1InValid:{'errorMsg':''}} } if((!currentControl.untouched||currentControl.dirty)&&password1Controller.value!=currentControl.value) { return {passwordNEQ:{'errorMsg':'两次密码输入不一致!'}} } }
创建指令后别忘了在app.module.ts里引入 :
@NgModule({
declarations: [
AppComponent,
...,
RepeatPasswordDirective
],
exports:[AppComponent],
imports: [
BrowserModule,
FormsModule,
HttpModule,
...
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
当然,如果使用ng g directive repeatPassword
命令创建指令,会自动添加。
模板:
<!--(ngSubmit)绑定的表单提交事件,ajax不需要--> <form #registerForm="ngForm" (ngSubmit)="doSubmit(registerForm.value)" > <div> <label for="userName">用户名:</label> <!--给input设置一个本地变量,可以读取errors显示错误信息--> <input type="text" id="userName" name="userName" [(ngModel)]="formData.userName" #userName="ngModel" required minlength="4"> <div *ngIf="userName.errors && (userName.dirty || userName.touched)" class="error"> <span [hidden]="!userName.errors.required">用户名必须输入</span> <span [hidden]="!userName.errors.minlength">用户名至少4位</span> </div> </div> <!--ngModelGroup指令可以给表单字段分组,值password是registerForm.value里该组的字段名,#passwordGroup是该组的本地变量名--> <fieldset ngModelGroup="passwordGroup" #passwordGroup="ngModelGroup" aria-required="true"> <label for="password1">密码:</label> <input type="password" id="password1" name="password1" [(ngModel)]="formData.password1" #password1="ngModel" required minlength="8"> <label for="password2">重复密码:</label> <!--使用自定义的校验器,加入repeatPassword指令,传入第一个密码输入框的ngModel,即用#password1="ngModel"声明的password1--> <input type="password" id="password2" name="password2" [(ngModel)]="formData.password2" [repeatPassword]="password1"> <span *ngIf="formErrors['passwordGroup.password2']" class="error"> {{ formErrors['passwordGroup.password2'] }} </span> </fieldset> <div> <label for="email">邮箱:</label> <input type="text" id="email" name="email" [(ngModel)]="formData.email" required pattern="[\w]+?@[\w]+?\.[a-z]+?"> <!-- 可以通过表单的onValueChanged事件,读到当前的错误信息,写到指定字段里 --> <div *ngIf="formErrors.email" class="error"> {{ formErrors.email }} </div> </div> <div> <label>性别:</label> <input type="radio" name="sex" [(ngModel)]="formData.sex" value="male" checked="checked"> 男 <input type="radio" name="sex" [(ngModel)]="formData.sex" value="female" > 女 </div> <fieldset ngModelGroup="nameGroup" #nameGroup="ngModelGroup"> <label>姓:</label> <input type="text" name="firstName" [(ngModel)]="formData.firstName" required><br /> <label>名:</label> <input type="text" name="lastName" [(ngModel)]="formData.lastName"> </fieldset> <button type="button" class="btn btn-default" [disabled]="!registerForm.valid" (click)="doSubmit(registerForm.value)">注册</button> </form> {{registerForm.value|json}}
组件:
import {Component, OnInit, ViewChild, AfterViewInit} from "@angular/core"; import {NgForm} from "@angular/forms"; @Component({ selector: 'app-form', templateUrl: './form.component.html', styleUrls: ['./form.component.css'] }) export class FormComponent implements OnInit,AfterViewInit { ngAfterViewInit(): void { //订阅表单值改变事件 this.registerForm.valueChanges.subscribe(data => this.onValueChanged(data)); } //找到表单 @ViewChild('registerForm') registerForm: NgForm; constructor() { } formData = {} as any; ngOnInit() { //默认性别为male this.formData.sex = "male"; } doSubmit(obj: any) { //表单提交 console.log(JSON.stringify(obj)); } onValueChanged(data) { for (const field in this.formErrors) { this.formErrors[field] = ''; //取到表单字段 const control = this.registerForm.form.get(field); //表单字段已修改或无效 if (control && control.dirty && !control.valid) { //取出对应字段可能的错误信息 const messages = this.validationMessages[field]; //从errors里取出错误类型,再拼上该错误对应的信息 for (const key in control.errors) { this.formErrors[field] += messages[key] + ''; } } } } //存储错误信息 formErrors = { 'email': '', 'userName': '', 'passwordGroup.password1':'', 'passwordGroup.password2':'', 'sex':'' }; //错误对应的提示 validationMessages = { 'email': { 'required': '邮箱必须填写.', 'pattern': '邮箱格式不对', }, 'userName': { 'required': '用户名必填.', 'minlength': '用户名太短', }, 'passwordGroup.password1':{ 'required': '请输入密码', 'minlength': '密码太短', }, 'passwordGroup.password2':{ 'required': '请重复输入密码', 'minlength': '密码太短', 'passwordNEQ':'两次输入密码不同', 'password1InValid':'' }, 'sex':{ 'required':'性别必填' } }; }