2
votes

@angular/forms: 6.1.7

I'm trying to create a custom validator that checks 2 formControl's for inconsistent.

When following the official angular documentation I get a error when inputting value to one of the 2 forms:

Uncaught Error: Expected validator to return Promise or Observable.
    at toObservable (forms.js:596)
    at Array.map (<anonymous>)
    at FormControl.asyncValidator (forms.js:584)
    at FormControl.push../node_modules/@angular/forms/fesm5/forms.js.AbstractControl._runAsyncValidator (forms.js:2454)
    at FormControl.push../node_modules/@angular/forms/fesm5/forms.js.AbstractControl.updateValueAndValidity (forms.js:2427)
    at FormControl.push../node_modules/@angular/forms/fesm5/forms.js.FormControl.setValue (forms.js:2764)
    at updateControl (forms.js:1699)
    at DefaultValueAccessor.onChange (forms.js:1684)
    at DefaultValueAccessor.push../node_modules/@angular/forms/fesm5/forms.js.DefaultValueAccessor._handleInput (forms.js:741)
    at Object.eval [as handleEvent] (ChangePasswordComponent.html:13)

It looks like angular is trying to fins a asyncValidator in this case, and not the sync version that i expect.

Worth to mention is that i also have tried to return a Observable<ValidationErrors | null> that gives me the same error output.

Validator:

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

export const passwordMatchValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
  const password = control.get('password');
  const confirmPassword = control.get('confirmPassword');

  if (!password || !confirmPassword) {
    return null;
  }

  return password === confirmPassword ? null : { passwordMismatch: true };
};

Implementation:

  this.formGroup = this.formBuilder.group(
      {
        password: ['', Validators.required, Validators.minLength(6)],
        confirmPassword: ['', Validators.required, Validators.minLength(6)]
      },
      {
        validators: passwordMatchValidator
      }

Question

How do i create a custom synchronous cross field validator?

Question on the side

Is it possible to pass in the formControl names to the function instead of hard coding them?

UPDATE: Final solution

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

export const matchValidator = (firstControlName: string, secondControlName: string): ValidatorFn => {
  return (control: FormGroup): ValidationErrors | null => {
      const firstControlValue = control.get(firstControlName).value;
      const secondControlValue =  control.get(secondControlName).value;
      if (!firstControlValue || !secondControlValue) {
          return null;
      }
      return firstControlValue === secondControlValue ? null : { mismatch: true };
  }
};

Implementation:

this.formGroup = this.formBuilder.group(
      {
        password: ['', [Validators.required, Validators.minLength(6)]],
        confirmPassword: ['', [Validators.required, Validators.minLength(6)]],
        currentPassword: ['', Validators.required]
      },
      {
        validator: matchValidator('password', 'confirmPassword')
      }
1

1 Answers

3
votes

Based on the documentation, you should pass validator property into extra param for group function in FormBuilder. See the docs. Second problem is that you should pass an array as second argument when creating form control and you set the validators directly with formBuilder:

password: ['', [Validators.required, Validators.minLength(6)]]

because currently the minLength validator is treated as asyncValidator as it is third argument.

Side question

You could create a validator factory function which creates the validator for you and takes 2 controls:

export const passwordMatchValidator = (passwordControl: AbstractControl, confirmPasswordControl: AbstractControl): ValidatorFn => { return (control: FormGroup): ValidationErrors | null => { const password = passwordControl.value; const confirmPassword = confirmPasswordControl.value; if (!password || !confirmPassword) { return null; } return password === confirmPassword ? null : { passwordMismatch: true }; } };

and usage:

passwordMatchValidator(this.formGroup.get('password'),this.formGroup.get('confirmPassword'));

or the factory function could just take string params instead of controls and extract the form controls itself.