1

I needed a validation that checks if email equals Email Confirm. For this I created a custom validation that looks like

import { AbstractControl, ValidationErrors, FormGroup } from '@angular/forms';

export function ValidateEqual( equals: string ) {
  return ( control: AbstractControl ): ValidationErrors | null => {

    const equalsField = control.root.get( equals )
    if ( equalsField ) {

      equalsField.valueChanges.subscribe( value => {
        console.log( 'observable fired', value );
        // control.updateValueAndValidity( { onlySelf: true  , emitEvent: false } );
      } );
      if ( control.value !== equalsField.value ) {
        return { equals: true };
      }
    }
    return null;
  }
}

and my form looks like

this.loginForm = this.fb.group( {
  password: [ '', [
    Validators.required,
  ] ],
  email: [ '', [
    Validators.required,
    Validators.email,
  ] ],
  emailConfirm: [ '', [
    Validators.required,
    Validators.email,
    ValidateEqual( 'email' ),
  ] ]
} );

So I am passing the field to check equality with as an argument in ValidateEqual(). That works fine.

The following scenario fails:

1)

Email: mattijs@foo.nl

Confirm Email: mattijs@food.nl

*now Confirm email shows an error that it is not equal to `Email'. Correct.

2)

*now I change the email field to match the confirm email field.

Email: mattijs@food.nl

Confirm Email: mattijs@food.nl

*The error on the confirm email field doesn't disappear because it is not aware of any change.

The issue I have is, that I am trying to use the valueChanges observable on the equals field to have the control field to re-validate, but when I enable control.updateValueAndValidity() it is subscribing exponentially on each keypress and the browser will crash. IT does notify the email confirm field to re-validate, so it almost works...almost.

Does anyone have an idea how to only have it subscribe once and make it re-validate the control field without subscribing again (and again...)?

Mattijs
  • 3,265
  • 3
  • 38
  • 35
  • check this one out: https://stackoverflow.com/questions/43487413/password-and-confirm-password-field-validation-angular2-reactive-forms – AT82 Nov 05 '17 at 07:30

4 Answers4

3

Here is a way of checking two email are same or not in your validate equal class write this code

export class emailValidation {
    static matchEmail(AC: AbstractControl){
        let email=AC.get('email').value;
        let emailConfirm=AC.get('emailConfirm').value;
        if(email!=emailConfirm){
            console.log(false);
        }
    }
}

and in your component add this code

this.loginForm = this.fb.group( {
  password: [ '', [
    Validators.required,
  ] ],
  email: [ '', [
    Validators.required,
    Validators.email,
  ] ],
  emailConfirm: [ '', [
    Validators.required,
    Validators.email,
  ] ]
},
{
    validator:passwordValidation.matchPassword
    } );
Abhishek Singh
  • 900
  • 7
  • 19
2

I would recommend you take a look at ngx-validation on github. It is a small open source lib, You can either use it directly or fork it, or simply see how they did it there:

A3io
  • 41
  • 5
1

The problem is that ValidatorFn placed on a FormControl will obviously only be checked when that FormControl's value changes. What we want is to check when either email or emailConfirm changes. To achieve this, we need to validate the FormGroup instead of just a FormControl. Because a ValidatorFn receives an AbstractControl that means our function can receive a FormGroup, FormControl or FormArray.

static matches(form: AbstractControl){
    return form.get('email').value == form.get('emailConfirm').value ? null : {equals: true};
}

Now, in your component, you still need to add this validator and it's a bit different:

this.form = this.fb.group({...}, {validator: CustomValidator.matches});

As you can see we can pass it a second object as an argument which takes validators for the entire FormGroup

In your template you would check to see if the entire form has the equals error instead of an individual FormControl:

<div *ngIf="form.hasError('equals')">The emails don't match</div>
Christian
  • 2,676
  • 3
  • 15
  • 25
  • though, in FormControl you can use **root** or **parent** fields to get your second FormControl – skorenb Mar 29 '18 at 12:57
0

Thanks guys for the good examples. I makes sense to do this on the form and not on a formControl since it doesn't know how to react/respond to other controls.

I made a slight tweak so that I can control the fields that are compared in the Equal validator. I also set the error on the field2 control so that I don't have to add a separate error message somewhere under my fields (I use Material design mat-form-fields):

interface IEmailMatchFields {
  field1: string;
  field2: string;
}
export class EmailMatch {

  fields: IEmailMatchFields;
  constructor( fields: IEmailMatchFields ) {
    this.fields = fields;
  }
  emailMatches( form: AbstractControl ) {

    if ( this.fields ) {
      if ( form.get( this.fields.field1 ).value == form.get( this.fields.field2 ).value ) {
        form.get( this.fields.field2 ).setErrors( { 'emailNotEq': false } );
        form.get( this.fields.field2 ).updateValueAndValidity( { onlySelf: true } );
        return null;
      } else {
        form.get( this.fields.field2 ).setErrors( { 'emailNotEq': true } );
        return { emailNotEq: true };
      }
    }
  }
}

export class MyComponent implements OnInit {
  emailMatch: EmailMatch;
  registerForm: FormGroup;

  constructor(
    private fb: FormBuilder
  ) {
    this.emailMatch = new EmailMatch( { field1: 'email', field2: 'emailConfirm' } );
  }

  ngOnInit(): void {
    this.registerForm = this.fb.group( {
      password: [ '', [
        Validators.required,
      ] ],
      email: [ '', [
        Validators.required,
        Validators.email,
      ] ],
      emailConfirm: [ '', [
        Validators.required,
        Validators.email,
      ] ]
    }, {
      validator: this.emailMatch.emailMatches.bind( this.emailMatch )
    } );
  }
}

I guess instead of field1 and field2 you could also pass in references of the 2 formcontrols but then you have to add the formcontrols to your formgroup etc.. I think sending the keys works fine.

Mattijs
  • 3,265
  • 3
  • 38
  • 35