Cross field validation in Angular2

20,268

Solution 1

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:

Solution 2

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

Solution 3

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

Share:
20,268

Related videos on Youtube

Coaden
Author by

Coaden

Leading development of a twitter sentiment analysis engine working with Python, NLTK (Natural Language Took Kit, PostgreSQL database, and the Django Web Framework. Also support and code several Android native apps.

Updated on July 09, 2022

Comments

  • Coaden
    Coaden almost 2 years

    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.

  • Alexander Zanfir
    Alexander Zanfir over 7 years
    This seems like the right way until you try to send form.value over to your back-end, and you realize its sending your matching passwords object with both passwords. Any elegant solutions beyond deleting matching passwords and adding password before sending it over?
  • TexasJetter
    TexasJetter over 7 years
    This solution is nice because it adds a global equal validator, unlike others that required re-writing code on each module. Note that the plunker code is different that what is posted here.
  • MarcS82
    MarcS82 over 7 years
    How do I define the password field in html? I found things like <div ngFormControl="matchingPassword"> which seems not to be up to date anymore.
  • eav
    eav about 7 years
    nice plunkr to understand. thanks i can use this one in my project.
  • Taras Budzyn
    Taras Budzyn about 7 years
    Little update here. In the newest Angular 2 ControlGroup was renamed to FormGroup
  • Kapil Soni
    Kapil Soni over 3 years
    sir i got 'validator' does not exist in type 'ValidatorFn error after use above?