如何在 Angular2 中实现自定义校验指令(确认密码) 已翻译 100%
我们会在本文中探索 Angular 2 内建的自定义验证。
# 介绍
Angular 2 原生就支持一些有用的验证器:
required: 验证字段必须存在
minlength: 验证字段值的最小长度有效
maxlength: 验证字段值的最大长度有效
pattern: 验证输入的值是否匹配给定的模板,比如 email
我们会基于下面的接口创建一个表单来获取用户信息。
// user.interface.ts export interface User { username: string; // required, must be 5-8 characters email: string; // required, must be valid email format password: string; // required, value must be equal to confirm password. confirmPassword: string; // required, value must be equal to password. }
需求
仅在字段数据不正确或提交表单的时候,为每个字段显示错误消息。
UI 展示:
# App 配置
这是我们的文件结构:
|- app/ |- app.component.html |- app.component.ts |- app.module.ts |- equal-validator.directive.ts |- main.ts |- user.interface.ts |- index.html |- styles.css |- tsconfig.json
为了使用新的表单模块,我们需要用 npm install @ angular/forms 指令调用 npm 包,并在应用程序模块中导入最新的表单模块。
$ npm install @angular/forms --save
下面是我们应用程序的 app.module.ts 模块:
// app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule, FormsModule ], // import forms module here declarations: [ AppComponent ], bootstrap: [ AppComponent ], }) export class AppModule { }

# App 组件
让我们继续创建 App 组件。
// app.component.ts import { Component, OnInit } from '@angular/core'; import { User } from './user.interface'; @Component({ moduleId: module.id, selector: 'app-root', templateUrl: 'app.component.html', styleUrls: ['app.component.css'] }) export class AppComponent implements OnInit { public user: User; ngOnInit() { // initialize model here this.user = { username: '', email: '', password: '', confirmPassword: '' } } save(model: User, isValid: boolean) { // 调用API保存customer console.log(model, isValid); } }
# HTML 视图
这是我们的 HTML 视图的样子。
<!-- app.component.html --> <div> <h1>Add user</h1> <form #f="ngForm" novalidate (ngSubmit)="save(f.value, f.valid)"> <!-- 我们将把验证的字段放在这里 --> <button type="submit" [disabled]="!myForm.valid">Submit</button> </form> </div>

# 实现
现在来一个个添加控件。
用户名
需求:必填,长度在 5-8 个字符之间
<!-- app.component.html --> ... <div> <label>Username</label> <input type="text" name="username" [ngModel]="user.username" required minlength="5" maxlength="8" #username="ngModel"> <small [hidden]="username.valid || (username.pristine && !f.submitted)"> Username is required (minimum 5 characters). </small> </div> <pre *ngIf="username.errors">{{ username.errors | json }}</pre> ...
required、minlength、maxlength 都是内置的验证器,所以很容易使用。
我们只会在用户名无效、获得焦点和提交表单的情况下显示错误消息。最后一条的 pre 标签在开发过程中对调试很有用。它会显示字段的所有验证错误。
电子邮件地址
需求:必填,必须是有效的电子邮件地址格式
<!-- app.component.html --> ... <div> <label>Email</label> <input type="email" name="email" [ngModel]="user.email" required pattern="^[a-zA-Z0–9_.+-]+@[a-zA-Z0–9-]+.[a-zA-Z0–9-.]+$" #email="ngModel" > <small [hidden]="email.valid || (email.pristine && !f.submitted)"> Email is required and format should be <i>john@doe.com</i>. </small> </div> ...
我们把 email 设置为必填,然后使用内建的模板验证器,通过正则表达式来检查 email 的值:^[a-zA-Z0–9_.+-]+@[a-zA-Z0–9-]+.[a-zA-Z0–9-.]+$.
密码和确认密码
需求:
密码:必填,值必须与确认密码的值相同。
确认密码:必填,值必须与密码的值相同。
<!-- app.component.html --> ... <div> <label>Password</label> <input type="password" name="password" [ngModel]="user.password" required #password="ngModel"> <small [hidden]="password.valid || (password.pristine && !f.submitted)"> Password is required </small> </div> <div> <label>Retype password</label> <input type="password" name="confirmPassword" [ngModel]="user.confirmPassword" required validateEqual="password" #confirmPassword="ngModel"> <small [hidden]="confirmPassword.valid || (confirmPassword.pristine && !f.submitted)"> Password mismatch </small> </div> ...
validateEqual 是我们自定义的验证器。它会将当前输入的值与输入的密码值进行对比验证。
# 自定义确认密码验证器
我们将制定一个 validate equal 指令。
// 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" class="form-control" name="password" [ngModel]="user.password" required validateEqual="confirmPassword" reverse="true"> <input type="password" class="form-control" name="confirmPassword" [ngModel]="user.confirmPassword" required validateEqual="password"> ...
reverse 是 false 或者没有设置的情况下,我们会像前一节提到的那样执行等值验证器。
reverse 是 true 的时候,我们仍然会执行等值验证器,但它不会为当前控件添加错误消息,而是为指定会把的目标控件添加错误消息。
在我们的例子中,我们设置 password 验证的 reverse 为 true。只要 password 与 retype password 的 值不等,我们会为确证密码字段添加一个错误消息,而不是重置 password 字段。
完整的自定义验证器代码如下:
// 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, @Attribute('reverse') public reverse: string) { } private get isReverse() { if (!this.reverse) return false; return this.reverse === 'true' ? true: false; } validate(c: AbstractControl): { [key: string]: any } { // self value let v = c.value; // control vlaue let e = c.root.get(this.validateEqual); // value not equal if (e && v !== e.value && !this.isReverse) { return { validateEqual: false } } // value equal and reverse if (e && v === e.value && this.isReverse) { delete e.errors['validateEqual']; if (!Object.keys(e.errors).length) e.setErrors(null); } // value not equal and reverse if (e && v !== e.value && this.isReverse) { e.setErrors({ validateEqual: false }); } return null; } }

# 总结
当然,还有其他方法也能解决密码和确认密码验证问题。有些人建议在组(stack overflow)中添加密码和确认密码的机制,然后验证它。
方法没有绝对的好与坏,适合自己的才是最好的。
更多细节请参考:
有关 @Attribute 的更多信息,请参阅 angular docs。
有关 Angular 2 指令的更多信息,请参阅此文档。
我们可以参考 Angular 2 验证器源码来了解它们是如何构建内置验证器的。
就这些。编码快乐!

评论删除后,数据将无法恢复
段落删除后无法恢复,确定取消删除此段落吗?
评论(2)