// equal-validator.directive.ts import { Directive, forwardRef, Attribute } from '@angular/core'; import { Validator, AbstractControl, NG_VALIDATORS } from '@angular/forms'; @Directive({ selector: '[validateEqual][formControlName],[validateEqual][formControl],[validateEqual][ngModel]', providers: [ { provide: NG_VALIDATORS, useExisting: forwardRef(() => EqualValidator), multi: true } ] }) export class EqualValidator implements Validator { constructor( @Attribute('validateEqual') public validateEqual: string) {} validate(c: AbstractControl): { [key: string]: any } { // self value (e.g. retype password) let v = c.value; // control value (e.g. password) let e = c.root.get(this.validateEqual); // value not equal if (e && v !== e.value) return { validateEqual: false } return null; } }
代码很长,让我们把它拆开一部分一部分地看。
申明指令
// equal-validator.directive.ts @Directive({ selector: '[validateEqual][formControlName],[validateEqual] [formControl],[validateEqual][ngModel]', providers: [ { provide: NG_VALIDATORS, useExisting: forwardRef(() => EqualValidator), multi: true } ] })
首先,我们使用 @Directive 注解定义指令。然后我们指定 selector。selector 是必须的。我们会扩展内建验证器集合 NG_VALIDATORS 来将我们的等值验证器用于 providers.
// equal-validator.directive.ts export class EqualValidator implements Validator { constructor( @Attribute('validateEqual') public validateEqual: string) {} validate(c: AbstractControl): { [key: string]: any } {} }
我们的指令类必须实现 Validator 接口。Validator 接口需要 avalidate 函数。在构建函数中,我们通过 @Attribute('validateEqual') 注解注入属性值,并将其赋值给 validateEqual 变量。在我们的示例中, validateEqual 的值是 "password" 。
实现验证
// equal-validator.directive.ts validate(c: AbstractControl): { [key: string]: any } { // 自己的值 (如 retype password) let v = c.value; // 控件的值 (如 password) let e = c.root.get(this.validateEqual); // 值不等旱 if (e && v !== e.value) return { validateEqual: false } return null; }
首先我们从输入控件读入值,赋给 v。然后我们在表单中找到 password 控件的值赋值给 e。之后检查值是否相等,如果不等就返回错。
# 在应用模块中导入自定义验证器
要使用自定义验证器,需要先将其导入到应用程序模块中。
// app.module.ts ... import { EqualValidator } from './equal-validator.directive'; // 导入验证器 import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent, EqualValidator ], // 导入到应用模块 bootstrap: [ AppComponent ], }) ...
好了!假如你在 password 字段中输入 "123",在 retype password 字段中输入"xyz",就会显示一个密码不匹配的错误。
# 看起来挺好,但是……
现在一切都挺好,直到你 在 retype passowrd 中输入文本之后 又修改了 password 字段的值。
比如,你在 password 字段中输入 "123",在 retype password 字段中也输入 "123", 然后将 password 字段的值改为 "1234" 。验证仍然通过。 为什么?
因为我们只把等值验证器应用到 retype password 字段。只有当 retype password 的值发生变化时才会触发验证。
解决办法
有几种方法可以解决这个问题。我们这里只讨论其中一种,你自己可以去找到其它办法。我们会再次使用 validateEqual 验证器并 添加 一个 reverse 属性。
<!-- app.component.html --> ... <input type="password" [ngModel]="user.password" required validateEqual="confirmPassword" reverse="true"> <input type="password" [ngModel]="user.confirmPassword" required validateEqual="password"> ...
reverse 是 false 或者没有设置的情况下,我们会像前一节提到的那样执行等值验证器。
reverse 是 true 的时候,我们仍然会执行等值验证器,但它不会为当前控件添加错误消息,而是 为指定 会把的目标控件添加错误消息 。