javascriptangulartypescriptangular-reactive-formscustom-validators

Angular forms custom validator null return not removing error from form


So I've created a custom validator to verify that the confirm password input matches the original entry. Works fine. However, I'm allowing that both fields be blank since this is on a page where someone can change their password in their settings, which of course they may not want to do. The form should become valid if both are blank so that it can be submitted and the other settings may be updated for their profile, but something weird is happening.

Form props:

passControl = new FormControl('', [
  FormValidation.checkPasswordStrength()
]);

passConfirmControl = new FormControl('', [
]);

profileForm = new FormGroup(
  {
    password: this.passControl,
    passwordConfirm: this.passConfirmControl
  },
  {
    validators: FormValidation.checkPasswordsMatch()
  }
);

Here's my validation function:

static checkPasswordsMatch(): ValidatorFn {
  return (control: AbstractControl): {[key: string]: any} => {
    let passwordCtrl = control.get('password');
    let passwordConfirmCtrl = control.get('passwordConfirm');

    // allow blank for when they don't want to change password
    if (passwordCtrl.value === '' && passwordConfirmCtrl.value === '') {
      console.log('both are blank');
      return null;
    }

    if (passwordCtrl.value !== passwordConfirmCtrl.value) {
      console.log('hit mismatch passwords');
      control.get('passwordConfirm').setErrors( { passwordMatch: true } );
    }

    return null;
  };
}

So when the form loads, its actually valid and I see both are blank. However, if you touch the password field and type something, and of course it shows that now they don't match, but when you delete what you typed there, then the function shows that they are again both empty, but the error sticks and the form stays invalid, unless you also touch the passwordConfirm field and also type something and delete it. What am I missing?


Solution

  • You are setting the error on the passwordConfirm FormControl, which your validator doesn't effect when it runs. If you want the error to go away, you'll either need to apply the validator to that specific FormControl, or you'll need to make sure you remove the Error from the control when the previous tests succeeds.

    Typically you would return the error within a validator rather than setting it on a specific FormControl. As you are finding out, unless you manually remove the error from the FormControl, it will stick around. Validators will only alter the error objects of the controls they are bound to.

    Typically you would just return the error if/when it occurs.

    return { passwordMatch: true };
    

    Or if you want to do both and set the FormControl error manually as well.

    // allow blank for when they don't want to change password
    if (passwordCtrl.value === '' && passwordConfirmCtrl.value === '') {
      console.log('both are blank');
      control.get('passwordConfirm').setErrors(null);
      return null;
    }
    
    if (passwordCtrl.value !== passwordConfirmCtrl.value) {
      console.log('hit mismatch passwords');
      control.get('passwordConfirm').setErrors( { passwordMatch: true } );
      return { passwordMatch: true }
    }
    

    Also just a nitpicky pedantic thing, you should try to make your errors logically readable. So while you are testing if the password matches, saying passwordMatch: true incinuates that the passwords match. You might be better off naming your errors after the error rather than the test, for example: passwordsDoNotMatch: true. This reads as though there is a password mismatch, which is a more accurate depiction of the issue being presented through the form.