模板驱动表单,指的是通过html5标准校验的表单,优势在于使用起来简单,但要动态修改验证器、操纵控制器模型不是很方便。
?
Angular2对表单处理做了一系列封装(模板驱动表单以及响应式表单):
数据绑定
这个自然不用说,使用ngModel可以双向绑定到组件里的对象字段。
控件状态检测
Angular会自动根据控件状态加上相应的class,如果我们需要编辑input标签在不同状态下的样式,只需要在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" >
<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" >
<label for ="password2">重复密码:</label>
<!--使用自定义的校验器,加入repeatPassword指令,传入第一个密码输入框的ngModel,即用#password1="ngModel"声明的password1-->
<input type="password" >
<span *ngIf="formErrors['passwordGroup.password2']" class="error">
{{ formErrors[ 'passwordGroup.password2'] }} </span>
</fieldset>
<div>
<label for ="email">邮箱:</label>
<input type="text" >
<!-- 可以通过表单的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测试数据ponent.html' ,
styleUrls: [ './form测试数据ponent.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':'性别必填'
}
};
}
?
查看更多关于Angular2 模板驱动表单校验的详细内容...