MichaelOryl MichaelOryl - 1 month ago 27
TypeScript Question

Performing cross-field validation on Angular 2 Reactive Forms

I'm trying to build out a registration form in Angular 2 using the Reactive Forms module. As such, I have a FormGroup defined for the form, and I can then list validators for each FormControl therein.

Consider this partial class:

export class TestFormComponent implements OnInit {
form: FormGroup;
password = new FormControl("", [Validators.required]);
passwordConfirm = new FormControl("", [Validators.required, this.validatePasswordConfirmation]);

constructor(private fb: FormBuilder) {
}

ngOnInit() {
this.form = this.fb.group({
"password": this.password,
"passwordConfirm": this.passwordConfirm
});
}

validatePasswordConfirmation(fc: FormControl) {
var pw2 = fc.value;
var pw = // how do I get this value properly????

if (pw === '') {
return {err:"Password is blank"};
}

if (pw2 === '') {
return {err:"Confirmation password is blank"};
}

if (pw !== pw2) {
return {err:"Passwords do not match"}
}

return null;
}
}


You can see I have a validator created for the
passwordConfirm
field, but I don't know how to get the value of the main
password
field (for use as
pw
in the validator) to do the comparison.

I can't just reference
this.form.value.password
because
this
in the validator doesn't refer to the main class that contains the form.

Any ideas?

Answer

So the answer turns out to be putting a new validator on the form as a whole, and then using the FormGroup object that is passed to the validator as a way to compare the field values. That much I had suspected. What I was missing, however, was how to set the error state properly on the individual passwordConfirm field. This code shows how to do it:

export class TestFormComponent implements OnInit {
  form: FormGroup;
  password = new FormControl("", [Validators.required]);
  passwordConfirm = new FormControl("", [Validators.required, this.validatePasswordConfirmation]);

  constructor(private fb: FormBuilder) {
  }

  ngOnInit() {
    this.form = this.fb.group({
      "password": this.password,
      "passwordConfirm": this.passwordConfirm
    },
    {
      validator: this.validatePasswordConfirmation
    });
  }

  validatePasswordConfirmation(group: FormGroup) {
    var pw = group.controls['password'];
    var pw2 = group.controls['passwordConfirm'];

    if (pw.value !== pw2.value) { // this is the trick
      pw2.setErrors({validatePasswordConfirmation: true});
    }

    // even though there was an error, we still return null
    // since the new error state was set on the individual field
    return null; 
  }
}

The trick, as mentioned in the comment in the code above, is that you can set error states on individual FormControl fields with the setErrors() method. So now, with this code in place, the confirmation field gets the proper valid/invalid state set based upon the regular validators it has, like Validators.required, as well as from the custom form based validator we added.

With this method, you could create complex form-based validators that can check the states of many different form fields and set validation states on each individually based on any business logic you can come up with. This makes cross-field validation with Angular 2 Reactive forms quite simple.

Comments