Angular 4.x 基于AbstractControl自定义表单验证
Angular 为我们提供了多种方式和 API,进行表单验证。接下来我们将介绍如何利用 Contents
What is a FormGroup我们先来看一下 Angular 4.x Reactive Forms 中,使用 signup-form.component.ts import { Component,OnInit } from '@angular/core'; import { FormBuilder,FormGroup,Validators } from '@angular/forms'; import { User } from './signup.interface'; @Component({...}) export class SignupFormComponent implements OnInit { user: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.user = this.fb.group({ name: ['',[Validators.required,Validators.minLength(2)]],account: this.fb.group({ email: ['',Validators.required],confirm: ['',Validators.required] }) }); } onSubmit({ value,valid }: { value: User,valid: boolean }) { console.log(value,valid); } } 上面示例中,我们通过 FormBuilder/FormGroup source codeFormBuilder source code// angular2/packages/forms/src/form_builder.ts 片段 @Injectable() class FormBuilder { // 基于controlsConfig、extra信息,创建FormGroup对象 group(controlsConfig: {[key: string]: any},extra: {[key: string]: any} = null): FormGroup {} // 基于formState、validator、asyncValidator创建FormControl对象 control( formState: Object,validator: ValidatorFn|ValidatorFn[] = null,asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null): FormControl {} //基于controlsConfig、validator、asyncValidator创建FormArray对象 array( controlsConfig: any[],validator: ValidatorFn = null,asyncValidator: AsyncValidatorFn = null): FormArray {} } 首先,我们先来看一下 group(controlsConfig: {[key: string]: any},extra: {[key: string]: any} = null): FormGroup {} 从 this.user = this.fb.group({ name: ['',Validators.required] }) }); 接下来我们来看一下 group(controlsConfig: {[key: string]: any},extra: {[key: string]: any} = null): FormGroup { // 创建controls对象集合 const controls = this._reduceControls(controlsConfig); // 获取同步验证器 const validator: ValidatorFn = extra != null ? extra['validator'] : null; // 获取异步验证器 const asyncValidator: AsyncValidatorFn = extra != null ? extra['asyncValidator'] : null; return new FormGroup(controls,validator,asyncValidator); } 我们在来看一下 _reduceControls(controlsConfig: {[k: string]: any}): {[key: string]: AbstractControl} { const controls: {[key: string]: AbstractControl} = {}; // controlsConfig - {name: [...],account: this.fb.group(...)} Object.keys(controlsConfig).forEach(controlName => { // 获取控件的名称,然后基于控件对应的配置信息,创建FormControl控件,并保存到controls对象上 controls[controlName] = this._createControl(controlsConfig[controlName]); }); return controls; } 继续看一下 _createControl(controlConfig: any): AbstractControl { if (controlConfig instanceof FormControl || controlConfig instanceof FormGroup || controlConfig instanceof FormArray) { return controlConfig; } else if (Array.isArray(controlConfig)) { // controlConfig - ['',Validators.minLength(2)]] const value = controlConfig[0]; // 获取初始值 // 获取同步验证器 const validator: ValidatorFn = controlConfig.length > 1 ? controlConfig[1] : null; // 获取异步验证器 const asyncValidator: AsyncValidatorFn = controlConfig.length > 2 ? controlConfig[2] : null; // 创建FormControl控件 return this.control(value,asyncValidator); } else { return this.control(controlConfig); } } 最后我们看一下 control( formState: Object,asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null): FormControl { return new FormControl(formState,asyncValidator); } 现在先来总结一下,通过分析 this.fb.group({...},{ validator: someCustomValidator }) 等价于 new FormGroup({...},someCustomValidator) 在我们实现自定义验证规则前,我们在来介绍一下 FormGroup source code// angular2/packages/forms/src/model.ts 片段 export class FormGroup extends AbstractControl { constructor( public controls: {[key: string]: AbstractControl},asyncValidator: AsyncValidatorFn = null) { super(validator,asyncValidator); this._initObservables(); this._setUpControls(); this.updateValueAndValidity({onlySelf: true,emitEvent: false}); } } 通过源码我们发现, AbstractControl接下来我们来看一下 // angular2/packages/forms/src/model.ts 片段 export abstract class AbstractControl { _value: any; ... private _valueChanges: EventEmitter<any>; private _statusChanges: EventEmitter<any>; private _status: string; private _errors: ValidationErrors|null; private _pristine: boolean = true; private _touched: boolean = false; constructor(public validator: ValidatorFn,public asyncValidator: AsyncValidatorFn) {} // 获取控件的valid状态,用于表示控件是否通过验证 get valid(): boolean { return this._status === VALID; } // 获取控件的invalid状态,用于表示控件是否通过验证 get invalid(): boolean { return this._status === INVALID; } // 获取控件的pristine状态,用于表示控件值未改变 get pristine(): boolean { return this._pristine; } // 获取控件的dirty状态,用于表示控件值已改变 get dirty(): boolean { return !this.pristine; } // 获取控件的touched状态,用于表示控件已被访问过 get touched(): boolean { return this._touched; } ... } 使用 AbstractControl 不是实现我们自定义 FormGroup 验证的关键,因为我们也可以注入 @Component({...}) export class SignupFormComponent implements OnInit { user: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.user = this.fb.group({ name: ['',Validators.required] }) }); } } 接下来我们要实现的自定义验证规则是,确保 email-matcher.ts export const emailMatcher = () => {}; 下一步,我们需要注入 export const emailMatcher = (control: AbstractControl): {[key: string]: boolean} => { }; 在 Angular 4.x Reactive Forms 文章中,我们介绍了通过 get(path: Array<string|number>|string): AbstractControl { return _find(this,path,'.'); } // 使用示例 - 获取sub-group的表单控件 this.form.get('person.name'); -OR- this.form.get(['person','name']); 具体示例如下: <div class="error" *ngIf="user.get('foo').touched && user.get('foo').hasError('required')"> This field is required </div> 了解完 AbstractControl,接下来我们来更新一下 export const emailMatcher = (control: AbstractControl): {[key: string]: boolean} => { const email = control.get('email'); const confirm = control.get('confirm'); }; 上面的示例中,control 表示的是 ? FormGroup {asyncValidator: null,_pristine: true,_touched: false,_onDisabledChange: Array[0],controls: Object…} ? FormControl {asyncValidator: null,_onDisabledChange: Array[1],_onChange: Array[1]…} ? FormControl {asyncValidator: null,_onChange: Array[1]…} Custom validation properties实际上 export const emailMatcher = (control: AbstractControl): {[key: string]: boolean} => { const email = control.get('email'); const confirm = control.get('confirm'); if (!email || !confirm) return null; if (email.value === confirm.value) { return null; } }; 上述代码意味着如果一切正常,我们都不会返回任何错误。现在我们需要添加自定义验证。 Custom validation Object hook我们先来看一下,在 HTML 模板中,我们自定义验证规则的预期使用方式: ... <div formGroupName="account"> <label> <span>Email address</span> <input type="email" placeholder="Your email address" formControlName="email"> </label> <label> <span>Confirm address</span> <input type="email" placeholder="Confirm your email address" formControlName="confirm"> </label> <div class="error" *ngIf="user.get('account').touched && user.get('account').hasError('nomatch')"> Email addresses must match </div> </div> ... 忽略掉其它无关的部分,我们只关心以下的代码片段: user.get('account').hasError('nomatch') 这意味着,我们需要先获取 account 对象 (FormGroup实例),然后通过 export const emailMatcher = (control: AbstractControl): {[key: string]: boolean} => { const email = control.get('email'); const confirm = control.get('confirm'); if (!email || !confirm) return null; return email.value === confirm.value ? null : { nomatch: true }; }; 最后,我们需要导入我们的自定义验证规则,然后在调用 ... import { emailMatcher } from './email-matcher'; ... ngOnInit() { this.user = this.fb.group({ name: ['',Validators.required] },{ validator: emailMatcher }) }); } ... 完整的示例代码如下: signup.interface.ts export interface User { name: string; account: { email: string; confirm: string; } } email-matcher.ts export const emailMatcher = (control: AbstractControl): {[key: string]: boolean} => { const email = control.get('email'); const confirm = control.get('confirm'); if (!email || !confirm) { return null; } return email.value === confirm.value ? null : { nomatch: true }; }; signup-form.component.ts import { Component,Validators } from '@angular/forms'; import { emailMatcher } from './email-matcher'; @Component({ selector: 'signup-form',template: ` <form class="form" novalidate (ngSubmit)="onSubmit(user)" [formGroup]="user"> <label> <span>Full name</span> <input type="text" placeholder="Your full name" formControlName="name"> </label> <div class="error" *ngIf="user.get('name').touched && user.get('name').hasError('required')"> Name is required </div> <div formGroupName="account"> <label> <span>Email address</span> <input type="email" placeholder="Your email address" formControlName="email"> </label> <label> <span>Confirm address</span> <input type="email" placeholder="Confirm your email address" formControlName="confirm"> </label> <div class="error" *ngIf="user.get('account').touched && user.get('account').hasError('nomatch')"> Email addresses must match </div> </div> <button type="submit" [disabled]="user.invalid">Sign up</button> </form> ` }) export class SignupFormComponent implements OnInit { user: FormBuilder; constructor(public fb: FormBuilder) {} ngOnInit() { this.user = this.fb.group({ name: ['',{ validator: emailMatcher }) }); } onSubmit({ value,valid }) { console.log(value,valid); } } 具体详情,可以查看线上示例。 参考资源
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |