Cross field validation in Angular2

2020-01-29 04:15发布

I'm building an Angular2 client side application. I'm currently working on the membership components and integrating client side components with MVC6 vNext Identity v3. I have written custom Angular2 password validators as follows:

needsCapitalLetter(ctrl: Control): {[s: string]: boolean} {
    if(!ctrl.value.match(/[A-Z]/))
        return {'needsCapitalLetter': true}

    return null;
}

needsLowerLetter(ctrl: Control): {[s: string]: boolean} {
    if(!ctrl.value.match(/[a-z]/))
        return {'needsLowerLetter': true}

    return null;            
}

needsNumber(ctrl: Control): {[s: string]: boolean} {
    if(!ctrl.value.match(/\d/))
        return {'needsNumber': true}

    return null;            
}

needsSpecialCharacter(ctrl: Control): {[s: string]: boolean} {
    if(!ctrl.value.match(/[^a-zA-Z\d]/))
        return {'needsSpecialCharacter': true}

    return null;            
}

This work great, and I'm loving Angular2, but now I'm trying to write a validator that verifies that the "Confirm Password" is equal to the "Password". In order to do this, I need to be able to validate one field against the other. I can easily do this at the component level, and just check on blur, or on submit, or any number of other ways, but this bypasses the Angular2 ngForm validation system. I would very much like to figure out how to write an Angular2 Validator for a field that can check the value of another field, by passing in the name of the other field or something close to this. It seems this should be a capability as this would be a necessity in almost any complex business application UI.

3条回答
甜甜的少女心
2楼-- · 2020-01-29 04:43

You need to assign a custom validator to a complete form group to implement this. Something like this:

this.form = this.fb.group({
  name:  ['', Validators.required],
  email: ['', Validators.required]
  matchingPasswords: this.fb.group({
    password:        ['', Validators.required],
    confirmPassword: ['', Validators.required]
  }, {validator: this.matchValidator})  <--------
});

This way you will have access to all controls of the group and not only one... This can be accessed using the controls property of the FormGroup. The FormGroup is provided when validation is triggered. For example:

matchValidator(group: FormGroup) {
  var valid = false;

  for (name in group.controls) {
    var val = group.controls[name].value
    (...)
  }

  if (valid) {
    return null;
  }

  return {
    mismatch: true
  };
}

See this question for more details:

查看更多
家丑人穷心不美
3楼-- · 2020-01-29 04:45

I haven't done this myself, but you could create ControlGroup with two password fields and validate it. Controls have .valueChanges property which is an observable and you can combine them and check for equality there.

Victor Savkin talks briefly about that exact case on Angular Air in this episode

查看更多
淡お忘
4楼-- · 2020-01-29 04:56

You can also use a custom directive validator to compare the fields.

In your 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>

And your directive:

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

Here is the complete solution in plunkr:

https://plnkr.co/edit/KgjSTj7VqbWMnRdYZdxM?p=preview

查看更多
登录 后发表回答