// user.interface.tsexport 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.}需求
# 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.tsimport { 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.component.tsimport { 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 herethis.user = { username: "", email: "", password: "", confirmPassword: ""} } save(model: User, isValid: boolean) {// 调用API保存customerconsole.log(model, isValid); }}# 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>现在来一个个添加控件。
<!-- 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 都是内置的验证器,所以很容易使用。
<!-- 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 是我们自定义的验证器。它会将当前输入的值与输入的密码值进行对比验证。
// equal-validator.directive.tsimport { 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 equalif (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.tsexport 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.tsvalidate(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",就会显示一个密码不匹配的错误。
<!-- 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">...
// equal-validator.directive.tsimport { 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 valuelet v = c.value;// control vlauelet e = c.root.get(this.validateEqual);// value not equalif (e && v !== e.value && !this.isReverse) { return {validateEqual: false }}// value equal and reverseif (e && v === e.value && this.isReverse) { delete e.errors["validateEqual"]; if (!Object.keys(e.errors).length) e.setErrors(null);}// value not equal and reverseif (e && v !== e.value && this.isReverse) { e.setErrors({ validateEqual: false });}return null; }}当然,还有其他方法也能解决密码和确认密码验证问题。有些人建议在组( stack overflow )中添加密码和确认密码的机制,然后验证它。