Custom Validator on reactive form for password and confirm password matching getting undefined parameters into Angular 4
Asked Answered
L

11

50

I'm trying to implement a custom validator to check if the password and password confirm are equal. The problem is that the validator is getting undefined password and confirmedPassword parameters. How do I make this work. The function works cause if I change the condition to === instead of !== it throws the error correctly when the fields are the same. Does anyone know which is the error here?

signup.component.html

<div class="col-md-7 col-md-offset-1 col-sm-7">
  <div class="block">
    <div class="well">
        <form (onSubmit)="onSubmit()" [formGroup]="signUpForm">
          <div class="form-group">
            <label for="username" class="control-label">Nombre de usuario:</label>
            <input type="text" class="form-control" formControlName="username"  title="Please enter your username" placeholder="username">
            <p class="help-block" *ngIf="signUpForm.get('username').hasError('required') && signUpForm.get('username').touched">El nombre de usuario es obligatorio</p>
            <p class="help-block" *ngIf="signUpForm.get('username').hasError('minlength') && signUpForm.get('username').touched">El nombre de usuario debe tener al menos 6 caracteres</p>
            <p class="help-block" *ngIf="signUpForm.get('username').hasError('maxlength') && signUpForm.get('username').touched">El nombre de usuario debe tener menos de 15 caracteres</p>
          </div>
          <div class="form-group">
            <label for="email" class="control-label">E-mail:</label>
            <input class="form-control" formControlName="email" title="Please enter your email" placeholder="[email protected]">
            <p class="help-block" *ngIf="signUpForm.get('email').hasError('required') && signUpForm.get('email').touched">La dirección de email es obligatoria</p>
            <p class="help-block" *ngIf="signUpForm.get('email').hasError('email') && signUpForm.get('email').touched">Debe ingresar una dirección de correo válida</p>
          </div>
          <div class="form-group">
            <label for="password" class="control-label">Contraseña:</label>
            <input type="password" class="form-control" formControlName="password" title="Please enter your password" [(ngModel)]="password">
            <p class="help-block" *ngIf="signUpForm.get('password').hasError('required') && signUpForm.get('password').touched">Debe ingresar una contraseña</p>
          </div>
          <div class="form-group">
            <label for="confirmedPassword" class="control-label">Confirmar Contraseña:</label>
            <input type="password" class="form-control" formControlName="confirmedPassword"  title="Please re-enter your password" [(ngModel)]="confirmedPassword">
            <p class="help-block" *ngIf="signUpForm.get('confirmedPassword').hasError('required') && signUpForm.get('confirmedPassword').touched">La confirmación de contraseña no puede estar vacía</p>
            <p class="help-block" *ngIf="signUpForm.get('confirmedPassword').hasError('passwordMismatch') && signUpForm.get('confirmedPassword').touched">Las contraseñas no coinciden</p>
          </div>
          <button type="submit" class="btn btn-success" [disabled]="!signUpForm.valid">Registrarse</button>
          <a routerLink="/signin" class="btn btn-default" style="">Ya tenes usuario? Logueate</a> {{ creationMessage }}
        </form>

      </div>

  </div>
</div>

signup.component.ts

import { Component, OnInit, ViewChild, Input } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { CustomValidators } from '../../shared/custom-validators';
import { Observable } from 'rxjs/Observable';

@Component({
  selector: 'app-signup',
  templateUrl: './signup.component.html',
  styleUrls: ['./signup.component.sass']
})
export class SignupComponent implements OnInit {

  signUpForm: FormGroup;
  user = {
    username: '',
    email: '',
    password: ''
  };
  submitted = false;
  @Input() password='';
  @Input() confirmedPassword='';

  constructor() { }

  ngOnInit() {

    this.signUpForm = new FormGroup({
      'username': new FormControl(null, [Validators.required, Validators.minLength(6), Validators.maxLength(15)]),
      'email': new FormControl(null, [Validators.required, Validators.email, Validators.minLength(5)]),
      'password': new FormControl(null, [Validators.required]),
      'confirmedPassword': new FormControl(null, [Validators.required, CustomValidators.passwordsMatch(this.password,this.confirmedPassword).bind(this)])
    });
  }

  onSubmit() {
    if (this.signUpForm.valid) {
      console.log(this.signUpForm.value);
    }
  }

}

custom-validators.ts

    import { FormControl } from '@angular/forms';

    export class CustomValidators{

    public static passwordsMatch(password: string, confirmedPassword: string) {

     return (control: FormControl) : { [s: string]: boolean } =>{
       //getting undefined values for both variables
       console.log(password,confirmedPassword);
        //if I change this condition to === it throws the error if the 
//  two fields are the same, so this part works
        if (password !== confirmedPassword) {
          return { 'passwordMismatch': true }
        } else {
          //it always gets here no matter what
          return null;
        }
    }
      }


    }
Laban answered 9/6, 2017 at 5:6 Comment(0)
N
61

import {AbstractControl, FormBuilder, FormGroup, Validators} from

set your password input into the group and no need to use "ngModel".

<div class="form-group row" formGroupName="passwords">
  <div class="form-group">
     <label for="password" class="control-label">Contraseña:</label>
     <input type="password" class="form-control" formControlName="password" title="Please enter your password">
     <p class="help-block" *ngIf="signUpForm.get('password').hasError('required') && signUpForm.get('password').touched">Debe ingresar una contraseña</p>
  </div>
  <div class="form-group">
     <label for="confirmedPassword" class="control-label">Confirmar Contraseña:</label>
     <input type="password" class="form-control" formControlName="confirmedPassword"  title="Please re-enter your password">
     <p class="help-block" *ngIf="signUpForm.get('confirmedPassword').hasError('required') && signUpForm.get('confirmedPassword').touched">Password must be required</p>
     <p class="help-block" *ngIf="signUpForm.get('confirmedPassword').hasError('passwordMismatch') && signUpForm.get('confirmedPassword').touched">password does not match</p>
  </div>

     buildForm(): void {
            this.userForm = this.formBuilder.group({
                passwords: this.formBuilder.group({
                    password: ['', [Validators.required]],
                    confirm_password: ['', [Validators.required]],
                }, {validator: this.passwordConfirming}),

            });
        }

add this custom function for validate password and confirm password

  passwordConfirming(c: AbstractControl): { invalid: boolean } {
    if (c.get('password').value !== c.get('confirm_password').value) {
        return {invalid: true};
    }
}

Display error when password does not match

<div style='color:#ff7355' *ngIf="userForm.get(['passwords','password']).value != userForm.get(['passwords','confirm_password']).value && userForm.get(['passwords','confirm_password']).value != null">
  Password does not match</div>
Notarize answered 9/6, 2017 at 5:18 Comment(5)
I liked this answer, one question, the group is set to invalid instead of the confirmation password field, how do I do to set the field when the mismatch password error is thrown with the class ng-invalid so I can style it with a red border? thanks!!Laban
yeah, thanks. I just did something different cause that shows the error right away, even if ng-touch class is not present on confirmed password. I just did this <p class="help-block" *ngIf="signUpForm.get('passwords').hasError('passwordMismatch') && signUpForm.get('passwords.confirmedPassword').touched">Passes don't match</p>Laban
and to color the confirm password field with red I added an [ngClass]="getPasswordConfirmValidityClass()" and on the component.ts //lighten up the input for confirm password when it's not equal to password getPasswordConfirmValidityClass() { const confirmedPasswordClass = this.signUpForm.get('passwords.password').touched && this.signUpForm.get('passwords.confirmedPassword').touched && this.signUpForm.get('passwords').hasError('passwordMismatch') ? 'error-border' : null; return confirmedPasswordClass; }Laban
Is this possible in react native. I am using react-reactive-form, and having an issue while confirming password. It's not working for react native please help.Mclaren
@ArchanaSharma this example may be helpfull codesandbox.io/s/p2rqmr8qk7Notarize
L
19

The issue is that you are mixing the reactive forms module with the input approach. This is causing you to get undefined when passing the values to the validator.

You don't need to bind to the ng-model when using the reactive forms. Instead, you should access the value of the fields from the Instance of FormGroup.

I do something like this in an app to validate the passwords match.

public Credentials: FormGroup;

ngOnInit() {
    this.Credentials = new FormGroup({});
    this.Credentials.addControl('Password', new FormControl('', [Validators.required]));
    this.Credentials.addControl('Confirmation', new FormControl(
        '', [Validators.compose(
            [Validators.required, this.validateAreEqual.bind(this)]
        )]
    ));
}

private validateAreEqual(fieldControl: FormControl) {
    return fieldControl.value === this.Credentials.get("Password").value ? null : {
        NotEqual: true
    };
}

Note that the validator expects a FormControl field as a parameter and it compares the value of the field to that of the value of the Password field of the Credentials FormGroup.

In the HTML make sure to remove the ng-model.

<input type="password" class="form-control" formControlName="confirmedPassword"  title="Please re-enter your password" >
<!-- AND -->
<input type="password" class="form-control" formControlName="password" title="Please enter your password">

Hope this helps!

Lollis answered 9/6, 2017 at 5:16 Comment(2)
I like this way, which sets the custom validator against the specific control.Shoemaker
This should be the accepted answer in my opinion for the placement of the validator on the form control in question (the confirm control), rather than the entire from group.Niphablepsia
S
10

There are two types of validators: FormGroup validator and FormControl validator. To verify two passwords match, you have to add a FormGroup validator. Below is my example:

Note: this.fb is the injected FormBuilder

this.newAccountForm = this.fb.group(
  {
    newPassword: ['', [Validators.required, Validators.minLength(6)]],
    repeatNewPassword: ['', [Validators.required, Validators.minLength(6)]],
  }, 
  {validator: this.passwordMatchValidator}
);

passwordMatchValidator(frm: FormGroup) {
  return frm.controls['newPassword'].value === frm.controls['repeatNewPassword'].value ? null : {'mismatch': true};
}

and in the templeate:

<div class="invalid-feedback" *ngIf="newAccountForm.errors?.mismatch && (newAccountForm.controls['repeatNewPassword'].dirty || newAccountForm.controls['repeatNewPassword'].touched)">
  Passwords don't match.
</div>

The key point here is to add the FormGroup validator as the second parameter to the group method.

Spavin answered 3/4, 2018 at 10:35 Comment(0)
S
8

Please update FormGroup code like below in Angular5

 this.signUpForm = new FormGroup({
      'username': new FormControl(null, [Validators.required, Validators.minLength(6), Validators.maxLength(15)]),
      'email': new FormControl(null, [Validators.required, Validators.email, Validators.minLength(5)]),
      'password': new FormControl(null, [Validators.required]),
      'confirmedPassword': new FormControl(null, [Validators.required])
    }, this.pwdMatchValidator);

Add pwdMatchValidator function in your component

pwdMatchValidator(frm: FormGroup) {
    return frm.get('password').value === frm.get('confirmedPassword').value
       ? null : {'mismatch': true};
 }

Add validation message in your template

<span *ngIf="confirmedPassword.errors || signUpForm .errors?.mismatch">
              Password doesn't match
            </span>

Please find the below angular material working component.

Component Templete Code password.component.html

 <form class="cahnge-pwd-form" (ngSubmit)="onSubmit()" name="passwordForm" [formGroup]="passwordForm" #formDir="ngForm">
      <div fxLayout='column'>
    <mat-form-field>
      <input matInput name="password" placeholder="Password" [type]="hide ? 'text' : 'password'" formControlName="password" required>
      <mat-icon matSuffix (click)="hide = !hide">{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
      <mat-error *ngIf="password.invalid && (password.dirty || password.touched || isSubmit)">
        <span *ngIf="password.errors.required">
          Please enter a Password.
        </span>
        <span *ngIf="password.errors.maxlength">
          Please enter a Email no more than 16 characters.
        </span>
        <span *ngIf="password.errors.minlength">
          Please enter a password at least 6 characters.
        </span>
      </mat-error>
    </mat-form-field>
    <mat-form-field>
      <input matInput name="password" placeholder="Confirm Password" [type]="confirm_hide ? 'text' : 'password'" formControlName="confirm_password"
        required>
      <mat-icon matSuffix (click)="confirm_hide = !confirm_hide">{{confirm_hide ? 'visibility_off' : 'visibility'}}</mat-icon>
      <mat-error *ngIf="(confirm_password.invalid && (confirm_password.dirty || confirm_password.touched || isSubmit) || passwordForm.errors?.mismatch)">
        <span *ngIf="confirm_password.errors || passwordForm.errors?.mismatch">
          Password doesn't match
        </span>
      </mat-error>
    </mat-form-field>
    <div fxLayout='row' fxLayoutGap="10px">
      <button type="submit" mat-raised-button color="primary">Submit</button>
      <button type="button" (click)="formDir.resetForm(passwordForm)" mat-raised-button color="warn">Cancel</button>
    </div>
  </div>
</form>

Component Code : password.component.ts

import { Component, OnInit, AfterViewInit } from '@angular/core';
import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { PasswordService } from './password.service';
import { PasswordValidation  } from './confirm';

@Component({
  selector: 'app-password',
  templateUrl: './password.component.html',
  styleUrls: ['./password.component.css']
})
export class PasswordComponent implements OnInit {
  passwordForm: FormGroup;
  isSubmit: boolean;
  constructor(private router: Router, private passwordService: PasswordService, private toastrService: ToastrService, private route: ActivatedRoute) { }
  ngOnInit() {
    this.passwordForm = new FormGroup({
      'password': new FormControl('', [
        Validators.required,
        Validators.minLength(6),
        Validators.maxLength(16),
      ]),
      'confirm_password': new FormControl('', [
         Validators.required,
        Validators.minLength(6),
        Validators.maxLength(16),
      ]),
    }, this.pwdMatchValidator);

  }
 pwdMatchValidator(frm: FormGroup) {
    return frm.get('password').value === frm.get('confirm_password').value
       ? null : {'mismatch': true};
 }

  get password() { return this.passwordForm.get('password'); }
  get confirm_password() { return this.passwordForm.get('confirm_password'); }

  onSubmit(formvalue):boolean {
    this.isSubmit = true;
    if (this.passwordForm.invalid) {
      return false;
    } else {
      this.passwordService.updatePassword(this.passwordForm.value)
      .subscribe((res) => {
        if (res.status == 'success') {
          this.toastrService.success(res.msg);
          this.router.navigate(['/change-password']);
        }
      })
      return true;
    }

  }

}
Smock answered 11/3, 2018 at 11:14 Comment(2)
This works for me as is newer version (Angular 5) answerSulfite
@CarlosMayoral Happy Coding!.Smock
T
4

I would do the same as Shailesh Ladumor but adding the following line before returning in the validation function:

c.get('confirm_password').setErrors({'noMatch': true});

So that the validation function looks like this:

passwordConfirming(c: AbstractControl): { invalid: boolean } {
    if (c.get('password').value !== c.get('confirm_password').value) {
        c.get('confirm_password').setErrors({'noMatch': true});
        return {invalid: true};
    }
}

This will not only set the hole userForm as an invalid form group, but it will also set confirm_password as an invalid form control.

With this you can later call the following function in your template:

public getPasswordConfirmationErrorMessage() {
if (this.userForm.get('confirm_password').hasError('required')) {
  return 'You must retype your password';
} else if (this.userForm.get('confirm_password').hasError('noMatch')) {
  return 'Passwords do not match';
} else {
  return '';
}

}

Turpitude answered 29/3, 2018 at 9:30 Comment(0)
P
3

When you need to validate on more than one field, and you wish to declare validator at form creation time, FormGroup validator must be used. The main issue with form validator is that it attaches error to form and not to validating control, which leeds to some inconsistents in template. Here is reusable form validator wich attaches error to both form and control

// in validators.ts file

export function controlsEqual(
  controlName: string,
  equalToName: string,
  errorKey: string = controlName // here you can customize validation error key 
) {

  return (form: FormGroup) => {
    const control = form.get(controlName);

    if (control.value !== form.get(equalToName).value) {
      control.setErrors({ [errorKey]: true });
      return {
        [errorKey]: true
      }
    } else {
      control.setErrors(null);
      return null
    }
  }
}

// then you can use it like
  ngOnInit() {
    this.vmForm = this.fb.group({
      username: ['', [Validators.required, Validators.email]],
      password: ['', [
        Validators.required,
        Validators.pattern('[\\w\\d]+'),
        Validators.minLength(8)]],
      confirm: [''], // no need for any validators here
    }, { 
        // here we attach our form validator
        validators: controlsEqual('confirm', 'password')
      });
  }

Pegmatite answered 21/6, 2019 at 10:9 Comment(1)
this is the best answer I guess :) If we add a custom validator on a single field, then there are chances that the validation won't run when other field field changes(as other will become dirty)Hebert
N
2
this.myForm = this.fb.group({
    userEmail: [null, [Validators.required, Validators.email]],
    pwd: [null, [Validators.required, Validators.minLength(8)]],
    pwdConfirm: [null, [Validators.required]],
}, {validator: this.pwdConfirming('pwd', 'pwdConfirm')});



pwdConfirming(key: string, confirmationKey: string) {
    return (group: FormGroup) => {
        const input = group.controls[key];
        const confirmationInput = group.controls[confirmationKey];
        return confirmationInput.setErrors(
            input.value !== confirmationInput.value ? {notEquivalent: true} : null
        );
    };
}
Nine answered 30/12, 2019 at 8:34 Comment(0)
K
1

Just want to add. I use Reactive form, so it is easy to use subscription for valueChanges on the form for custom validation

this.registrationForm.valueChanges.subscribe(frm => {
  const password = frm.password;
  const confirm = frm.confirmPassword;
  if (password !== confirm) {
    this.registrationForm.get('confirmPassword').setErrors({ notMatched: true });
  }
  else {
    this.registrationForm.get('confirmPassword').setErrors(null);
  }
});

}

Keyser answered 8/8, 2021 at 5:46 Comment(0)
I
1
 import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
        
       public newForm: FormGroup | any;
        
        ngOnInit(): void {
              this.newForm = new FormGroup({
              password: new FormControl(null, [Validators.required]),
              confirm: new FormControl(null, [Validators.required])
            }, {validators: this.validateAreEqual})
          }
        
           
        
            public validateAreEqual(c: AbstractControl): {notSame: boolean} {
                return  c.value.password  ===  c.value.confirm ? {notSame: false} : {notSame: true};
             }
        
         
    

<span class="error-text"  *ngIf="newForm.get('confirm').touched &&  newForm.errors['notSame']">Password  mismatch </span>

            
Infection answered 4/11, 2022 at 16:20 Comment(0)
E
0

When you're creating the validator, you're passing in the values of password and confirmedPassword, but changes in those values will not be reflected in the validator.

The two options I see are:

  1. define your validator on the FormGroup, and look up the values from the two controls you want to compare; or
  2. since you're binding to this already, use this.password and this.confirmedPassword in your validator.
Elvia answered 9/6, 2017 at 5:17 Comment(1)
I went with the option 2..what happens if the function is in another file like my use case?? I tried to use . this.password and this.confirmedPassword as a condition but it doesn't recognize those properties on the CustomValidators class....if I put that on the same file as the component.ts then I have to repeat the same validator each time I have to create a formLaban
M
0

Just for the variety, I'm adding one more way to do this. Basically, I created a simple custom validator that takes the original control (first entered field) and checks it's value with the re-entry control (second control).

import { AbstractControl, ValidatorFn } from "@angular/forms";

export abstract class ReEnterValidation {
   static reEnterValidation(originalControl: AbstractControl): ValidatorFn {
       return (control: AbstractControl): { [s: string]: boolean } => {
           if (control.value != originalControl.value) {
               return { 'reentry': true };
           }
       };
   }
}

You can either set this validator initially when creating a new form control:

control1 = new FormControl('', ReEnterValidation.reEnterValidation(control2));

Or, you can set it afterwards:

control.setValidators(ReEnterValidation.reEnterValidation(this._newPinControl));
control.updateValueAndValidity();

In the view, you can show the errors as following:

<!-- Confirm pin validation -->
<div *ngIf="control2.invalid && (control2.dirty || control2.touched)">
    <div *ngIf="control2.errors.reentry">
        The re-entered password doesn't match. Please try again.
    </div>
</div>
Myrilla answered 25/4, 2020 at 10:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.