文档结构  
可译网翻译有奖活动正在进行中,查看详情 现在前往 注册?
原作者:Jecelyn Yeen    来源:scotch.io [英文]
zhongzhong    计算机    2017-01-05    0评/654阅
翻译进度:已翻译   参与翻译: zhongzhong (15)

#介绍

Angular 2 支持的几个非常有用的本地校验器:

  1. required: 验证字段是否强制(不能为空)
  2. minlength: 验证字段的最小长度
  3. maxlength: 验证字段的最大长度
  4. pattern: 验证输入的值是否符合定义的正则表达式, 例如 邮箱

我们将基于这个接口构建一个表单用来获取用户信息:


// 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.
}
第 1 段(可获 0.74 积分)

需求

只有当字段值变化或者表单提交的时候才显示错误信息。

用户界面看起来像这样:Angular 2 Custom Validator Directive

#应用设置

这是我们的文件结构:

|- 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 install @angular/forms --save

这里是我们的应用程序模块 app.module.ts:

第 2 段(可获 0.73 积分)
// 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.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) {
        // call API to save customer
        console.log(model, isValid);
    }
}
第 3 段(可获 0.15 积分)

#HTML视图

我们来看下我们的HTML视图。

<!-- app.component.html -->

<div>
    <h1>Add user</h1>
    <form #f="ngForm" novalidate (ngSubmit)="save(f.value, f.valid)">
        <!-- we will place our fields here -->
        <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>

...
第 4 段(可获 0.35 积分)

因为内置了requiredminlengthmaxlength这些校验器,使用非常简单。

如果用户名无效,字段被触摸或表单被提交,我们将只显示错误消息。 最后一行的 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>

...
第 5 段(可获 0.76 积分)

我们将邮箱设置为必填的, 然后使用内置的pattern校验器来校验这个值是否符合邮箱正则: ^[a-zA-Z0–9_.+-]+@[a-zA-Z0–9-]+.[a-zA-Z0–9-.]+$.

密码和确认密码

要求:

  1. 密码:必填, 值必须等于确认密码。
  2. 确认密码: 必填, 值必须等于密码。
<!-- 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>
...
第 6 段(可获 0.53 积分)

validateEqual是我们自定义的校验器。它能够校验当前输入的值和密码的值。

#自定义确认密码校验器

我们将开发一个指令来用于校验。

// 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;
    }
}
第 7 段(可获 0.31 积分)

代码很长, 让我们分解成一段一段的来看。

指令声明

// equal-validator.directive.ts

@Directive({
    selector: '[validateEqual][formControlName],[validateEqual] 
    [formControl],[validateEqual][ngModel]',
    providers: [
        { provide: NG_VALIDATORS, useExisting: forwardRef(() => EqualValidator), multi: true }
    ]
})

首先,我们使用 @Directive注解来声明一个指令。 然后我们指令selector选择器。 选择器是必要的属性。我们将通过在providers中扩展内置验证器 NG_VALIDATORS,来使用我们的相等校验器。

第 8 段(可获 0.56 积分)

类定义

// equal-validator.directive.ts

export class EqualValidator implements Validator {
    constructor( @Attribute('validateEqual') public validateEqual: string) {}

    validate(c: AbstractControl): { [key: string]: any } {}
}

我们的指令类应该实现Validator接口。 Validator接口声明了一个validate函数。 在我们的构造函数中,我们通过 @Attribute(‘validateEqual’)注释将属性的值注入,并赋值到validateEqual变量中。 在我们的例子中,validateEqual的值就是“password”。

第 9 段(可获 0.51 积分)

validate函数的实现:

// equal-validator.directive.ts

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;
}

首先, 我们读取输入属性的值并赋值给v。 然后我们查找password控件,并将其赋值给e。 之后,我们检查是否相等,如果不相等返回错误。

第 10 段(可获 0.53 积分)

#导入自定义的校验器到app模块中

要使用我们自定义的校验器,需要将它导入到应用模块中。

// app.module.ts
...
import { EqualValidator } from './equal-validator.directive';  // import validator
import { AppComponent }   from './app.component';

@NgModule({
  imports:      [ BrowserModule, FormsModule ],
  declarations: [ AppComponent, EqualValidator ], // import to app module
  bootstrap:    [ AppComponent ],
})

...

假设你在密码输入框中输入“123” 然后在确认密码中输入“xyz”,应该就会消失你的密码不匹配的cu

第 11 段(可获 0.59 积分)

#似乎一切都ok了, 但是…

一切都正常工作,直到你在确认密码输入框中输入完,然后修改密码输入框的值的时候(问题来了)。

例如,你输入“123”到密码框中,然后输入“123”到确认密码输入框中,然后去改变密码输入框中的值为“1234”。校验仍然通过。这是为什么? 

这是因为我们只把校验器添加到了确认密码输入框控件中。这样只有确认密码输入框中的值改变了,校验才会触发。

解决方法

有几种方法来解决这个问题。我们将讨论其中的一个解决方案。 其它的方案我留给你自己去发现。我们将重用validateEqual校验器,并添加一个叫做reverse的属性。

第 12 段(可获 1.38 积分)
<!-- 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或者没有设置值的时候, 我们将按照前面章节的解释进行同样的校验。
  • 当reverser为true时, 我们仍然会执行相等校验, 但是我们会添加错误信息到确认密码控件中,取代之前设置到当前控件中的方式。
第 13 段(可获 0.51 积分)

在我们的例子中,我们设置password 校验的reverse属性为true。 当密码不等于确认密码的时候, 我们将设置一个错误到确认密码字段,而不是重置密码字段。

完整的自定义校验器代码:

// 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;
    }
}
第 14 段(可获 0.43 积分)

#总结

还有其他的方法来解决密码和确认密码验证。 有人建议将密码和确认密码添加到一组中 (stack overflow)然后验证它们。

没有对或错,这是取决于你。

更多的细节:

第 15 段(可获 1.04 积分)

文章评论