0
votes

I have a standard change password page, so you input your old password, then the new one and finally repeat the new one .

On the "new password" I have multiple custom validators, and I need one more to check if the "new password" is equal to the "old password".

But of course I cannot do that from FormControl because I don't have access to the "old password" FormControl, so I need to do the validation on FormGroup.

But what happens is, when it validates this part on FormGroup, then suddenly all validators on the "new password" FormControl are triggered!!

Is there a reason?

 this.myForm = this.formBuilder.group(
      {
        oldPassword: [
          "",
          {
            validators: [Validators.required],
        ],
        newPassword: [
          "",
          Validators.compose([
            Validators.required,
            CustomValidators.patternValidator(/\d/, { hasNumber: true }),
            CustomValidators.patternValidator(/[A-Z]/, {
              hasCapitalCase: true,
            }),
            CustomValidators.patternValidator(/[a-z]/, { hasSmallCase: true }),
            CustomValidators.patternValidator(
              /[ !@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/,
              { hasSpecialCharacters: true }
            ),
            Validators.minLength(8),
          ]),
        ],
        confirmNewPassword: ["", Validators.compose([Validators.required])],
      },
      { validators: [CustomValidators.passwordMatchValidator] }
    );
  }

my Custom passwordMatchValidator:

static passwordMatchValidator(control: AbstractControl) {
    const password: string = control.get("newPassword").value; 
    const confirmPassword: string = control.get("confirmNewPassword").value; 
    const oldPassword: string = control.get("oldPassword").value;

    if (password !== confirmPassword) {
      control.get("confirmNewPassword").setErrors({ NoPassswordMatch: true });
    }

    if (password === oldPassword) {

   control.get("newPassword").setErrors({ OldPasswordMatch: true });
  }
  }

so basically, if this FormGroup validator triggers the "OldPasswordMatch" error, then suddenly, all the specific Validators on newPassword FormControl will get triggered, why is that?

I cannot put this validation on the FormControl "new password" because it has no access to the FormControl "old password"

1
not sure what you're trying to say here. all validators on a form control / group are evaluated all the time unless you set a different updateOn option. regardless of what you're doing at the form group level. if a form control is marked as required, if it has no value, it will be marked as invalid and as having a required error no matter what - bryan60
what I'm telling you is, until I add the second part on passwordMatchValidator (the one that checks password === oldPassword) all works fine, as soon as that is added, whenever those 2 password matches, not only that Validation error will trigger, but also all errors from the FormControl validation above, so for example the Validators.minLength(8) will trigger, even if the matching password have only 2 characters!! why is this happening? - AJ989
it's triggering because your password only has 2 characters and the minlength is 8?? - bryan60

1 Answers

1
votes

not sure what you're saying regarding "all validators triggering" as they all validate all the time, but regardless, you're not implementing a group level validator correctly. validators shouldn't access controls and set errors, they return null or an error object... as it's a group level validator, it should be more like:

static passwordMatchValidator(control: AbstractControl) {
    const password: string = control.get("newPassword").value; 
    const confirmPassword: string = control.get("confirmNewPassword").value; 
    const oldPassword: string = control.get("oldPassword").value;

    let errors = null;

    if (password !== confirmPassword) {
      errors = { NoPassswordMatch: true };
    }

    if (password === oldPassword) {
      errors = Object.assign(errors || {}, { OldPasswordMatch: true });
    }

    return errors;
}

then this error will appear on the group. and you need to take that into account in your template / user prompts.

to make your inputs aware of the error with angular material, you must implement an ErrorStateMatcher:

import {FormControl, FormGroupDirective, NgForm} from '@angular/forms';
import {ErrorStateMatcher} from '@angular/material/core';

export class GroupErrorStateMatcher implements ErrorStateMatcher {

  constructor(private checkError: string) {}

  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const groupError = form && form.hasError(this.checkError);
    return !!(control && (control.invalid || groupError) && (control.dirty || control.touched));
  }
}

use in component:

oldPasswordMatch =  new GroupErrorStateMatcher('OldPasswordMatch')
noPasswordMatch =  new GroupErrorStateMatcher('NoPassswordMatch')

which you attach to the respective inputs:

<input matInput [errorStateMatcher]="oldPasswordMatch" formControlName="newPassword">

<input matInput [errorStateMatcher]="noPasswordMatch" formControlName="confirmNewPassword">