Angular 4.x 自定义验证指令

前端之家收集整理的这篇文章主要介绍了Angular 4.x 自定义验证指令前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

表单是几乎每个 Web 应用程序的一部分。虽然 Angular 为我们提供了几个内置 validators (验证器),但在实际工作中为了满足项目需求,我们经常需要为应用添加一些自定义验证功能。接下来我们将着重介绍,如何自定义 validator 指令。

Built-in Validators

Angular 提供了一些内置的 validators,我们可以在 Template-DrivenReactive 表单中使用它们。如果你对 Template-Driven 和 Reactive 表单还不了解的话,可以参考 Angular 4.x Forms 系列中 Template Driven FormsReactive Forms 这两篇文章

在写本文时,Angular 支持的内建 validators 如下:

  • required - 设置表单控件值是非空的

  • email - 设置表单控件值的格式是 email

  • minlength - 设置表单控件值的最小长度

  • maxlength - 设置表单控件值的最大长度

  • pattern - 设置表单控件的值需匹配 pattern 对应的模式

在使用内建 validators 之前,我们需要根据使用的表单类型 (Template-Driven 或 Reactive),导入相应的模块,对于 Template-Driven 表单,我们需要导入 FormsModule。具体示例如下:

  1. import { NgModule } from '@angular/core';
  2. import { BrowserModule } from '@angular/platform-browser';
  3. import { FormsModule } from '@angular/forms';
  4.  
  5. @NgModule({
  6. imports: [BrowserModule,FormsModule],// we add FormsModule here
  7. declarations: [AppComponent],bootstrap: [AppComponent]
  8. })
  9. export class AppModule {}

一旦导入了 FormsModule 模块,我们就可以在应用中使用该模块提供的所有指令:

  1. <form novalidate>
  2. <input type="text" name="name" ngModel required>
  3. <input type="text" name="street" ngModel minlength="3">
  4. <input type="text" name="city" ngModel maxlength="10">
  5. <input type="text" name="zip" ngModel pattern="[A-Za-z]{5}">
  6. </form>

而对于 Reactive 表单,我们就需要导入 ReactiveFormsModule 模块:

  1. import { ReactiveFormsModule } from '@angular/forms';
  2.  
  3. @NgModule({
  4. imports: [BrowserModule,ReactiveFormsModule],...
  5. })
  6. export class AppModule {}

可以直接使用 FormControlFormGroup API 创建表单:

  1. @Component()
  2. class Cmp {
  3.  
  4. form: FormGroup;
  5.  
  6. ngOnInit() {
  7. this.form = new FormGroup({
  8. name: new FormControl('',Validators.required)),street: new FormControl('',Validators.minLength(3)),city: new FormControl('',Validators.maxLength(10)),zip: new FormControl('',Validators.pattern('[A-Za-z]{5}'))
  9. });
  10. }
  11. }

也可以利用 FormBuilder 提供的 API,采用更便捷的方式创建表单:

  1. @Component()
  2. class Cmp {
  3.  
  4. constructor(private fb: FormBuilder) {}
  5.  
  6. ngOnInit() {
  7. this.form = this.fb.group({
  8. name: ['',Validators.required],street: ['',Validators.minLength(3)],city: ['',Validators.maxLength(10)],zip: ['',Validators.pattern('[A-Za-z]{5}')]
  9. });
  10. }
  11. }

需要注意的是,我们还需要使用 [formGroup] 指令将表单模型与 DOM 中的表单对象关联起来,具体如下:

  1. <form novalidate [formGroup]="form">
  2. ...
  3. </form>

接下来我们来介绍一下如何自定义 validator 指令。

Building a custom validator directive

在实际开发前,我们先来介绍一下具体需求:我们有一个新增用户的表单页面,里面包含 4 个输入框,分为用于保存用户输入的 usernameemailpasswordconfirmPassword 信息。具体的 UI 效果图如下:

Setup (基础设置)

1.定义 user 接口

  1. export interface User {
  2. username: string; // 必填,5-8个字符
  3. email: string; // 必填,有效的email格式
  4. password: string; // 必填,值要与confirmPassword值一样
  5. confirmPassword: string; // 必填,值要与password值一样
  6. }

2.导入 ReactiveFormsModule

app.module.ts

  1. import { NgModule,CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
  2. import { BrowserModule } from '@angular/platform-browser';
  3. import { ReactiveFormsModule } from '@angular/forms';
  4.  
  5. import { AppComponent } from './app.component';
  6.  
  7. @NgModule({
  8. imports: [BrowserModule,declarations: [AppComponent],bootstrap: [AppComponent]
  9. })
  10. export class AppModule { }

3.初始化 AppComponent

app.component.html

  1. <div>
  2. <h3>Add User</h3>
  3. <form novalidate (ngSubmit)="saveUser()" [formGroup]="user">
  4. <div>
  5. <label for="">Username</label>
  6. <input type="text" formControlName="username">
  7. <div class="error" *ngIf="user.get('username').invalid &&
  8. user.get('username').touched">
  9. Username is required (minimum 5 characters,maximum 8 characters).
  10. </div>
  11. <!--<pre *ngIf="user.get('username').errors" class="margin-20">
  12. {{ user.get('username').errors | json }}</pre>-->
  13. </div>
  14. <div>
  15. <label for="">Email</label>
  16. <input type="email" formControlName="email">
  17. <div class="error" *ngIf="user.get('email').invalid && user.get('email').touched">
  18. Email is required and format should be <i>24065****@qq.com</i>.
  19. </div>
  20. <!--<pre *ngIf="user.get('email').errors" class="margin-20">
  21. {{ user.get('email').errors | json }}</pre>-->
  22. </div>
  23. <div>
  24. <label for="">Password</label>
  25. <input type="password" formControlName="password">
  26. <div class="error" *ngIf="user.get('password').invalid &&
  27. user.get('password').touched">
  28. Password is required
  29. </div>
  30. <!--<pre *ngIf="user.get('password').errors" class="margin-20">
  31. {{ user.get('password').errors | json }}</pre>-->
  32. </div>
  33. <div>
  34. <label for="">Retype password</label>
  35. <input type="password" formControlName="confirmPassword" validateEqual="password">
  36. <div class="error" *ngIf="user.get('confirmPassword').invalid &&
  37. user.get('confirmPassword').touched">
  38. Password mismatch
  39. </div>
  40. <!--<pre *ngIf="user.get('confirmPassword').errors" class="margin-20">
  41. {{ user.get('confirmPassword').errors | json }}</pre>-->
  42. </div>
  43. <button type="submit" class="btn-default" [disabled]="user.invalid">Submit</button>
  44. </form>
  45. </div>

app.component.ts

  1. import { Component,OnInit } from '@angular/core';
  2. import { FormGroup,FormBuilder,Validators } from '@angular/forms';
  3.  
  4. export interface User {
  5. username: string; // 必填,5-8个字符
  6. email: string; // 必填,有效的email格式
  7. password: string; // 必填,值要与confirmPassword值一样
  8. confirmPassword: string; // 必填,值要与password值一样
  9. }
  10.  
  11. @Component({
  12. moduleId: module.id,selector: 'exe-app',templateUrl: 'app.component.html',styles: [`
  13. .error {
  14. border: 1px dashed red;
  15. color: red;
  16. padding: 4px;
  17. }
  18.  
  19. .btn-default {
  20. border: 1px solid;
  21. background-color: #3845e2;
  22. color: #fff;
  23. }
  24.  
  25. .btn-default:disabled {
  26. background-color: #aaa;
  27. }
  28.  
  29. `]
  30. })
  31. export class AppComponent implements OnInit {
  32. public user: FormGroup;
  33.  
  34. constructor(public fb: FormBuilder) { }
  35.  
  36.  
  37. ngOnInit() {
  38. this.user = this.fb.group({
  39. username: ['',[Validators.required,Validators.minLength(5),Validators.maxLength(8)]],email: ['',Validators.email]],password: ['',[Validators.required]],confirmPassword: ['',[Validators.required]]
  40. });
  41. }
  42.  
  43. saveUser(): void {
  44.  
  45. }
  46. }

Custom confirm password validator

接下来我们来实现自定义 equal-validator 指令:

equal-validator.directive.ts

  1. import { Directive,forwardRef,Attribute } from '@angular/core';
  2. import { Validator,AbstractControl,NG_VALIDATORS } from '@angular/forms';
  3.  
  4. @Directive({
  5. selector: '[validateEqual][formControlName],[validateEqual][formControl],[validateEqual][ngModel]',providers: [
  6. { provide: NG_VALIDATORS,useExisting: forwardRef(() => EqualValidator),multi: true }
  7. ]
  8. })
  9. export class EqualValidator implements Validator {
  10. constructor(@Attribute('validateEqual') public validateEqual: string) { }
  11.  
  12. validate(c: AbstractControl): { [key: string]: any } {
  13. // self value (e.g. retype password)
  14. let v = c.value; // 获取应用该指令,控件上的值
  15.  
  16. // control value (e.g. password)
  17. let e = c.root.get(this.validateEqual); // 获取进行值比对的控件
  18.  
  19. // value not equal
  20. if (e && v !== e.value)
  21. return {
  22. validateEqual: false
  23. }
  24. return null;
  25. }
  26. }

上面的代码很长,我们来分解一下。

Directive declaration

  1. @Directive({
  2. selector: '[validateEqual][formControlName],[validateEqual]
  3. [formControl],multi: true }
  4. ]
  5. })

首先,我们使用 @Directive 装饰器来定义指令。然后我们设置该指令的 Metadata 信息:

  • selector - 定义指令在 HTML 代码中匹配的方式

  • providers - 注册EqualValidator

其中 forwardRef 的作用,请参考 - Angular 2 Forward Reference

Class defintion

  1. export class EqualValidator implements Validator {
  2. constructor(@Attribute('validateEqual') public validateEqual: string) {}
  3.  
  4. validate(c: AbstractControl): { [key: string]: any } {}
  5. }

我们的 EqualValidator 类必须实现 Validator 接口:

  1. export interface Validator {
  2. validate(c: AbstractControl): ValidationErrors|null;
  3. registerOnValidatorChange?(fn: () => void): void;
  4. }

该接口要求定义一个 validate() 方法,因此我们的 `EqualValidator 类中就需要实现 Validator 接口中定义的 validate 方法。此外在构造函数中,我们通过 @Attribute('validateEqual') 装饰器来获取 validateEqual 属性上设置的值。

Validate implementation

  1. validate(c: AbstractControl): { [key: string]: any } {
  2. // self value (e.g. retype password)
  3. let v = c.value; // 获取应用该指令,控件上的值
  4.  
  5. // control value (e.g. password)
  6. let e = c.root.get(this.validateEqual); // 获取进行值比对的控件
  7.  
  8. // value not equal
  9. if (e && v !== e.value)
  10. return { // 若不相等,返回验证失败信息
  11. validateEqual: false
  12. }
  13. return null;
  14. }

Use custom validator

要在我们的表单中使用自定义验证器,我们需要将其导入到我们的应用程序模块中。

  1. import { NgModule,CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
  2. import { BrowserModule } from '@angular/platform-browser';
  3. import { ReactiveFormsModule } from '@angular/forms';
  4.  
  5. import { EqualValidator } from './equal-validator.directive';
  6.  
  7. import { AppComponent } from './app.component';
  8.  
  9. @NgModule({
  10. imports: [BrowserModule,declarations: [AppComponent,EqualValidator],bootstrap: [AppComponent]
  11. })
  12. export class AppModule { }

以上代码成功运行后,我们来验证一下刚实现的功能

友情提示:演示需要先把密码框的类型设置为text

  • 步骤一

  • 步骤二

看起来一切很顺利,但请继续看下图:

什么情况,password 输入框的值已经变成 12345 了,还能验证通过。为什么会出现这个问题呢?因为我们的只在 confirmPassword 输入框中应用 validateEqual 指令。所以 password 输入框的值发生变化时,是不会触发验证的。接下来我们来看一下如何修复这个问题。

Solution

我们将重用我们的 validateEqual 验证器并添加一个 reverse 属性

  1. <div>
  2. <label for="">Password</label>
  3. <input type="text" formControlName="password" validateEqual="confirmPassword"
  4. reverse="true">
  5. <div class="error" *ngIf="user.get('password').invalid &&
  6. user.get('password').touched">
  7. Password is required
  8. </div>
  9. <!--<pre *ngIf="user.get('password').errors" class="margin-20">
  10. {{ user.get('password').errors | json }}</pre>-->
  11. </div>
  12. <div>
  13. <label for="">Retype password</label>
  14. <input type="text" formControlName="confirmPassword" validateEqual="password">
  15. <div class="error" *ngIf="user.get('confirmPassword').invalid &&
  16. user.get('confirmPassword').touched">
  17. Password mismatch
  18. </div>
  19. <!--<pre *ngIf="user.get('confirmPassword').errors" class="margin-20">
  20. {{ user.get('confirmPassword').errors | json }}</pre>-->
  21. </div>
  • 若未设置 reverse 属性属性值为 false,实现的功能跟前面的一样。

  • reverse 的值设置为 true,我们仍然会执行相同的验证,但错误信息不是添加到当前控件,而是添加到目标控件上。

在上面的示例中,我们设置 password 输入框的 reverse 属性为 true,即 reverse="true"。当 password 输入框的值与 confirmPassword 输入框的值不相等时,我们将把错误信息添加到 confirmPassword 控件上。具体实现如下:

equal-validator.directive.ts

  1. import { Directive,multi: true }
  2. ]
  3. })
  4. export class EqualValidator implements Validator {
  5. constructor(@Attribute('validateEqual') public validateEqual: string,@Attribute('reverse') public reverse: string) { }
  6.  
  7. private get isReverse() {
  8. if (!this.reverse) return false;
  9. return this.reverse === 'true';
  10. }
  11.  
  12. validate(c: AbstractControl): { [key: string]: any } {
  13. // self value
  14. let v = c.value;
  15.  
  16. // control vlaue
  17. let e = c.root.get(this.validateEqual);
  18.  
  19. // value not equal
  20. // 未设置reverse的值或值为false
  21. if (e && v !== e.value && !this.isReverse) {
  22. return {
  23. validateEqual: false
  24. }
  25. }
  26.  
  27. // value equal and reverse
  28. // 若值相等且reverse的值为true,则删除validateEqual异常信息
  29. if (e && v === e.value && this.isReverse) {
  30. delete e.errors['validateEqual'];
  31. if (!Object.keys(e.errors).length) e.setErrors(null);
  32. }
  33.  
  34. // value not equal and reverse
  35. // 若值不相等且reverse的值为true,则把异常信息添加到比对的目标控件上
  36. if (e && v !== e.value && this.isReverse) {
  37. e.setErrors({ validateEqual: false });
  38. }
  39. return null;
  40. }
  41. }

以上代码运行后,成功解决了我们的问题。其实解决该问题还有其它的方案,我们可以基于 passwordconfirmPassword 来创建 FormGroup 对象,然后添加自定义验证来实现上述的功能。详细的信息,请参考 - Angular 4.x 基于AbstractControl自定义表单验证

参考资源

猜你在找的Angularjs相关文章