RemyaJ
RemyaJ

Reputation: 5526

password and confirm password field validation angular2 reactive forms

I need to check whether password and confirm password fields have same value using reactive form angular2. I did see a lot of answers on the same here,Angular 2 form validating for repeat password ,Comparing fields in validator with angular2, but none seemed to work for me.Can someone please help."this" is undefined in my validate function :( . Sharing my code,

this.addEditUserForm = this.builder.group({
            firstName: ['', Validators.required],
            lastName: ['', Validators.required],
            title: ['', Validators.required],
            email: ['', Validators.required],
            password: ['', Validators.required],
            confirmPass: ['', [Validators.required, this.validatePasswordConfirmation]]
        });
validatePasswordConfirmation(group: FormGroup): any{
        let valid = true;
        // if (this.addEditUserForm.controls.password != this.addEditUserForm.controls.confirmPass) {
        //     valid = false;
        //     this.addEditUserForm.controls.confirmPass.setErrors({validatePasswordConfirmation: true});
        // }
        return valid;
    }

Upvotes: 31

Views: 55637

Answers (12)

bobbyg603
bobbyg603

Reputation: 3840

An equals validator can be done in a way that's mostly type-safe.

equals-validator.ts

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

export function createEqualsValidator(control: FormControl, matchControl: FormControl): ValidatorFn {
  return () => (control?.value === matchControl?.value ? null : { mismatch: true });
}

app.component.ts

import { Component } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { createEqualsValidator } from './equals-validator';

const upperLowerSymbolNumberRegex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*\W)/;

@Component({
  selector: 'password-change',
  templateUrl: './change.component.html',
  styleUrls: ['./change.component.scss'],
})
export class ChangeComponent {
  formGroup: FormGroup<PasswordChangeForm>;

  currentPasswordFormControl: FormControl<string>;
  newPasswordFormControl: FormControl<string>;
  confirmNewPasswordFormControl: FormControl<string>;

  get passwordMatchError() {
    return this.formGroup.errors?.mismatch && this.confirmNewPasswordFormControl?.touched;
  }

  constructor(formBuilder: FormBuilder) {
    const validators = [Validators.required, Validators.minLength(8), Validators.pattern(upperLowerSymbolNumberRegex)];

    this.currentPasswordFormControl = formBuilder.control('', validators);
    this.newPasswordFormControl = formBuilder.control('', validators);
    this.confirmNewPasswordFormControl = formBuilder.control('', validators);

    this.formGroup = formBuilder.group(
      {
        currentPassword: this.currentPasswordFormControl,
        newPassword: this.newPasswordFormControl,
        confirmNewPassword: this.confirmNewPasswordFormControl,
      },
      {
        validators: createEqualsValidator(this.newPasswordFormControl, this.confirmNewPasswordFormControl),
      }
    );
  }
}

interface PasswordChangeForm {
  currentPassword: FormControl<string>;
  newPassword: FormControl<string>;
  confirmNewPassword: FormControl<string>;
}

Upvotes: 0

Faouzi
Faouzi

Reputation: 1019

Angular 12 update :

Since the solutions above didn't work for me, here's the implementation for password match validation for angular 12

Custom validator

export function mustMatch(controlName: string, matchingControlName: string) {
  return (formGroup: FormGroup) => {
    const control = formGroup.controls[controlName];
    const matchingControl = formGroup.controls[matchingControlName];

    if (matchingControl.errors && !matchingControl.errors.mustMatch) {
      return;
    }

    // set error on matchingControl if validation fails
    if (control.value !== matchingControl.value) {
      matchingControl.setErrors({ mustMatch: true });
    } else {
      matchingControl.setErrors(null);
    }
    return null;
  };
}

component.ts

initPasswordForm() {
   this.passwordForm = new FormGroup({
     password: new FormControl(null, [
     Validators.required,
     Validators.minLength(6)
  ]),
     passwordConfirmation: new FormControl(null, [
     Validators.required,
     Validators.minLength(6),
  ]),
 },
  mustMatch('password', 'passwordConfirmation') 
  );
}

components.html

<span *ngIf="passwordForm.get('passwordConfirmation').errors?.'mustMatch']"> The password confirmation does not match the entered password</span>
               

Upvotes: 1

Jonathan
Jonathan

Reputation: 4689

Here is a match validator I use:

export function matchValidator(
  matchTo: string, 
  reverse?: boolean
): ValidatorFn {
  return (control: AbstractControl): 
  ValidationErrors | null => {
    if (control.parent && reverse) {
      const c = (control.parent?.controls as any)[matchTo] 
        as AbstractControl;
      if (c) {
        c.updateValueAndValidity();
      }
      return null;
    }
    return !!control.parent &&
      !!control.parent.value &&
      control.value === 
      (control.parent?.controls as any)[matchTo].value
      ? null
      : { matching: true };
  };
}

and you can use it like so:

password: ['', [
  Validators.required,
  Validators.pattern('^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]+)$'),
  Validators.minLength(6),
  Validators.maxLength(25),
  matchValidator('confirmPassword', true)
]],
confirmPassword: ['', [
  Validators.required,
  matchValidator('password')
]],

It will only display the error message on confirmPassword but will check for it on both fields.

<mat-error *ngIf="confirmPassword.hasError('matching')">
Password must match.
</mat-error>

See here for more info.

J

Upvotes: 4

AVJT82
AVJT82

Reputation: 73357

Best would be to have a nested group inside the form group, where we have a custom validator checking the form group with password and confirmPass, so when either of the fields are changed, the validator is fired, as of previously it only fires when confirmPass field is modified.

So instead do something like this inside the outer formgroup:

// ...
passwords: this.fb.group({
  password: ['', [...]],
  confirmPass: ['', [...]]
}, {validators: this.checkPasswords}) // add a validator for the whole group
// ...

and then the validator could look like this:

checkPasswords: ValidatorFn = (group: AbstractControl):  ValidationErrors | null => { 
  let pass = group.get('password').value;
  let confirmPass = group.get('confirmPassword').value

  return pass === confirmPass ? null : { notSame: true }
}

Showing the validation error could then be done like this:

*ngIf="addEditUserForm.hasError('notSame', 'passwords')"

Of course you don't need to have a nested group, but it's better to not have the custom validator fire every time when any changes happen to the form. This way it's only fired when changes happen to this inner form group.

Upvotes: 40

manu
manu

Reputation: 41

Using Reactive Forms - I think this is the simple way

change-password.ts

passwordChangeForm = new FormGroup(
    {
      currentPassword: new FormControl("", [
        Validators.required,
        Validators.minLength(6),
      ]),
      newPassword: new FormControl("", [
        Validators.required,
        Validators.minLength(6),
      ]),
      confirmNewPassword: new FormControl("", [
        Validators.required,
        Validators.minLength(6),
      ]),
    },
    {
      validators: (control) => {
        if (control.value.newPassword !== control.value.confirmNewPassword) {
          control.get("confirmNewPassword").setErrors({ notSame: true });
        }
        return null;
      },
    }
  );

change-password.html

<div>
  <h1 mat-dialog-title>Change Password</h1>
  <form [formGroup]="passwordChangeForm">
    <mat-form-field appearance="outline">
      <mat-label>Current Password</mat-label>
      <input matInput formControlName="currentPassword" type="password" />
    </mat-form-field>
    <mat-form-field appearance="outline">
      <mat-label>New Password</mat-label>
      <input matInput formControlName="newPassword" type="password" />
    </mat-form-field>
    <mat-form-field appearance="outline">
      <mat-label>Confirm New Password</mat-label>
      <input matInput formControlName="confirmNewPassword" type="password" />
      <mat-error
        *ngIf="passwordChangeForm.get('confirmNewPassword').hasError('notSame')"
      >
        New Password Doesn't match
      </mat-error>
    </mat-form-field>
  </form>
  <button
    mat-raised-button
    color="primary"
    (click)="changePassword()"
    [disabled]="passwordChangeForm.invalid"
  >
    Change Password
  </button>
</div>

Upvotes: 2

Srikar Phani Kumar M
Srikar Phani Kumar M

Reputation: 1384

Your answer should work just fine. All you need to do is add .value

Like here:

this.addEditUserForm = this.builder.group({
            firstName: ['', Validators.required],
            lastName: ['', Validators.required],
            title: ['', Validators.required],
            email: ['', Validators.required],
            password: ['', Validators.required],
            confirmPass: ['', [Validators.required, this.validatePasswordConfirmation]]
        });
validatePasswordConfirmation(group: FormGroup): any{
        let valid = true;
         if (this.addEditUserForm.controls.password**.value** != this.addEditUserForm.controls.confirmPass**.value**) {
             valid = false;
           this.addEditUserForm.controls.confirmPass.setErrors({validatePasswordConfirmation: true});
        // }
        return valid;
    }

Upvotes: 1

Nils K&#228;hler
Nils K&#228;hler

Reputation: 3001

I had some problems implementing this, and when i got it implemented with a custom validator and errorStateMatcher, i got the problem that the formbuilder.group function were deprecated, but after some inspection I found that my validator had to change to comply to the group function.

The Controller looks like this:

// ...
addEditUserForm: FormGroup;
passwordMatcher = new ComparisonErrorStateMatcher();

constructor(private formBuilder: FormBuilder) {
  this.addEditUserForm = this.formBuilder.group({
    password: ['', Validators.required],
    confirmPass: ['', Validators.required],
  }, { validators: [MatchValidator.validate] }); // add validator to group
}
// ...

My validator looked like this:

export class MatchValidator {
  // (group: AbstractControl) is the form group but needs to be AbstractControl instead of (group: FormGroup) to remove deprecation warning. 
  static validate(group: AbstractControl): ValidationErrors | null { 
    const password = group.get('password')?.value;
    const confirmPassword = group.get('confirmPass')?.value;

    return password === confirmPass ? null : { notMatching: true }
  };
}

And my ErrorStateMatcher looked like this:

export class ComparisonErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const invalidCtrl = !!(control && control.invalid && control.parent?.dirty);
    const invalidParent = !!(control && control.parent && control.parent.invalid && control.parent.dirty);

    return (invalidCtrl || invalidParent) && (control?.touched ?? false);
  }
}

Lastly the HTML would have to look something like this:

<form [formGroup]="addEditUserForm">

  <mat-form-field >
    <mat-label>password</mat-label>
    <input formControlName="password"
           matInput />
    <mat-error *ngIf="newPasswordForm.hasError('required')">
      password is required
    </mat-error>
  </mat-form-field>

  <mat-form-field>
    <mat-label>confirm password</mat-label>
    <input formControlName="confirmPass"
           [errorStateMatcher]="passwordMatcher"
           matInput />
    <mat-error *ngIf="newPasswordForm.hasError('notMatching')">
      passwords don't match
    </mat-error>
  </mat-form-field>
</form>

This creates a form with two input fields that are required to match.

Upvotes: 3

Strider
Strider

Reputation: 3749

For those who want to add a custom validator without being forced to pass from the form group validation, it's possible to add the validator after defining the form.

One advantage of this approach is that the error is added to the form control and not to the form group. This way it's more easy to display the error associated to the field since we can check the error directly on the field/form control itself.

This is how I implemented it:

Custom validator

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

export class MismatchValidator {

  static mismatch(otherInputControl: AbstractControl): ValidatorFn {

    return (inputControl: AbstractControl): { [key: string]: boolean } | null => {
      if (inputControl.value !== undefined
        && inputControl.value.trim() != ""
        && inputControl.value !== otherInputControl.value) {
        return { 'mismatch': true };
      }

      return null;
    };
  }
}

Applying the custom validator to the form control

  ngOnInit() {
    this.initForm();
    // The validators are set after defining the form in order to be able to access the password control and pass it to the custom validator as a parameter
    this.setValidators();
  }

  private initForm() {
    this.myForm = this.formBuilder.group({
      password: new FormControl(''),
      passwordConfirmation: new FormControl('')
    });
  }

  private setValidators() {
    const formValidators = {
      "password": Validators.compose([
        Validators.required,
        //....
      ]),
      "passwordConfirmation": Validators.compose([
        Validators.required,
        MismatchValidator.mismatch(this.myForm.get("password")) 
      ])
    }

    this.passwordRecoveryForm.get("password").setValidators(
      formValidators["password"]
    );
    this.passwordRecoveryForm.get("passwordConfirmation").setValidators(
      formValidators["passwordConfirmation"]
    );
  }

The validators are set after defining the form in order to be able to access the password control and pass it to the custom validator as a parameter.

Upvotes: 3

Demian S
Demian S

Reputation: 309

If you don't want to go through a custom validator you can just create a input field that is standalone and therefore will not compute in your formGroup but rather through ngModel

<input type="password" matInput [(ngModel)]="passwordConfirm" [ngModelOptions]="{standalone: true}">

Then in your ts you can just validate and throw error or invalidate the form if you want. Just found it slightly quicker and practical:

// Check Passwords Match

  if (this.staffAccountForm.value.password !== this.passwordConfirm) {
    this.snackbar.snackBarSimple('Passwords do not match.');
    return false;
  }

Upvotes: 0

Alexander Baron
Alexander Baron

Reputation: 51

I did a different approach that will work for any control. First I define the basic controls of the form:

    this.myForm = this.formBuilder.group({
            name: ['', Validators.required],
            password: ['', Validators.required],
    });

Then I create a new control to confirm the value with my custom validator:

    const confirmPasswordControl = new FormControl('', {
            validator: sameValueAs(this.myForm, 'password')
    });

    this.myForm.addControl('confirmPassword', confirmPasswordControl);

The code of the sameValueAs validator is as follows, you can define it on a separte file to be used anywhere

export function sameValueAs(group: FormGroup, controlName: string): ValidatorFn {
    return (control: FormControl) => {
          const myValue = control.value;
          const compareValue = group.controls[controlName].value;

          return (myValue === compareValue) ? null : {valueDifferentFrom:controlName};

    };
}

Upvotes: 1

RemyaJ
RemyaJ

Reputation: 5526

This is what eventually worked for me -

this.addEditUserForm = this.builder.group({
            firstName: ['', Validators.required],
            lastName: ['', Validators.required],
            title: ['', Validators.required],
            email: ['', Validators.required],
            password: ['', Validators.required],
            confirmPass: ['', Validators.required]
        },{validator: this.checkIfMatchingPasswords('password', 'confirmPass')});



checkIfMatchingPasswords(passwordKey: string, passwordConfirmationKey: string) {
          return (group: FormGroup) => {
            let passwordInput = group.controls[passwordKey],
                passwordConfirmationInput = group.controls[passwordConfirmationKey];
            if (passwordInput.value !== passwordConfirmationInput.value) {
              return passwordConfirmationInput.setErrors({notEquivalent: true})
            }
            else {
                return passwordConfirmationInput.setErrors(null);
            }
          }
        }

Upvotes: 42

Amit
Amit

Reputation: 4353

If you want to do it that way, you need bind the function to the current "this" context. Pass over this.validatePasswordConfirmation.bind(this) but note that this function will be passed the FormControl for the confirmation, and not the FormGroup like you stated in the function signature.

Upvotes: 1

Related Questions