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?
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.
pw2.setErrors(null);
causes problems if the pw2 field has validators on itself, by itself, such as minLength
:
ngOnInit() {
this.form = this.fb.group({
"password": [this.password, Validators.minLength(6)],
"passwordConfirm": [this.passwordConfirm, Validators.minLength(6)]
},
{
validator: this.validatePasswordConfirmation
});
}
The setErrors(null)
will destroy the minLength warning.
It's best if the cross-field validator validatePasswordConfirmation()
returns the error instead, because where the error appears in the HTML -- beside an individual field, or above/below the form as a whole -- is totally in our control anyway.
<div *ngIf="myNgForm.submitted" class="text-error">
<span *ngIf="form.errors?.validatePasswordConfirmation">This field must match the first field</span>
<span *ngIf="form.controls.passwordConfirm.errors?.minlength">must be at least 6 chars</span>
</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With