如何在 Angular2 中实现自定义校验指令(确认密码) 已翻译 100%

oschina 投递于 2017/01/16 16:29 (共 6 段, 翻译完成于 01-22)
阅读 2324
收藏 14
2
加载中

我们会在本文中探索 Angular 2 内建的自定义验证。

# 介绍

Angular 2 原生就支持一些有用的验证器:

  1. required: 验证字段必须存在

  2. minlength: 验证字段值的最小长度有效

  3. maxlength: 验证字段值的最大长度有效

  4. 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 展示:

边城
边城
翻译于 2017/01/21 11:37
0

# 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 { }

Tocy
Tocy
翻译于 2017/01/17 09:17
0

# 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>

总长
总长
翻译于 2017/01/16 23:35
0

# 实现

现在来一个个添加控件。

用户名

需求:必填,长度在 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-.]+$.

密码和确认密码

需求:

  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>
...

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",就会显示一个密码不匹配的错误。

边城
边城
翻译于 2017/01/21 12:27
0

# 看起来挺好,但是……

现在一切都挺好,直到你在 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;
    }
}

边城
边城
翻译于 2017/01/21 12:40
1

# 总结

当然,还有其他方法也能解决密码和确认密码验证问题。有些人建议在组(stack overflow)中添加密码和确认密码的机制,然后验证它。

方法没有绝对的好与坏,适合自己的才是最好的。

更多细节请参考:

就这些。编码快乐!

Tocy
Tocy
翻译于 2017/01/17 09:32
0
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(2)

酷聊_爱你美
酷聊_爱你美
es浏览器编辑
全体人员
全体人员
email的校验正则有问题
返回顶部
顶部