模板驅動表單,指的是通過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':'性別必填'
}
};
}
