Angular Cross Field Validation
What is cross field validation?
It is validating one form control based on the value of another ๐
Imagine you want to create a simple range component. You start by building the FormGroup
in your component class, and you follow it up with proper html bindings.
@Component({
selector: 'range',
template: `
<form [formGroup]="form">
<input formControlName="rangeStart" placeholder="Range start" type="number">
<input formControlName="rangeEnd" placeholder="Range end" type="number">
</form><div> Valid: {{ form.valid ? '๐' : '๐' }} </div>`
})
export class AppComponent {
form: FormGroup; constructor(private fb: FormBuilder) {
this.form = this.fb.group({
rangeStart: [null, Validators.required],
rangeEnd: [null, Validators.required]
});
}
}
Great! We have a working form. Letโs input some numbers and see if it works.
As we quickly notice, our FormGroup
is valid, even if the rangeStart
control has a value higher than the rangeEnd
control. Dear Lord. What kind of range is this. ๐ฑ
The solution is simple. We should just add a validator to the rangeStart
control, which checks the value of the rangeEnd
control. Next we should add another validator to the rangeEnd
control, which checks the value of the rangeStart
control. In each validator, we compare the values to validate the controls. Right?
Wrong.
The simplest solution is to move the validation to the ancestor control. In this case it is our FormGroup
control.
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
rangeStart: [null, Validators.required],
rangeEnd: [null, Validators.required]
}, { validator: MyAwesomeRangeValidator});
}
All that is left to do is to implement the MyAwersomeRangeValidator
. It is really straightforward. We just have to implement the ValidatorFn
interface:
interface ValidatorFn {
(c: AbstractControl): ValidationErrors | null
}
As you can see itโs a simple function that receives the AbstractForm
as the only argument (FormGroup
in our case) and returns either null
if the form is valid, or ValidationErrors
object if the form is invalid.
Here is the example implementation
const MyAwesomeRangeValidator: ValidatorFn = (fg: FormGroup) => {
const start = fg.get('rangeStart').value;
const end = fg.get('rangeEnd').value; return start !== null && end !== null && start < end
? null
: { range: true };
};
Other than some boring null checks itโs as simple as comparing the values from both child controls. Notice we cannot use !!start && !!end && start < end
, because 0 is falsy in JavaScript and our range might contain 0 values and still be valid.
Also the null checks are necessary, because I donโt know whether 0 < null is true and I am too afraid to find out. ๐ป
And thatโs all there is to it. We have a working range component with proper cross validation.
Complete example ๐ฅ๐ฅ๐ฅ
import { Component } from '@angular/core';
import { ValidatorFn, FormBuilder, FormGroup, Validators } from '@angular/forms';const MyAwesomeRangeValidator: ValidatorFn = (fg: FormGroup) => {
const start = fg.get('rangeStart').value;
const end = fg.get('rangeEnd').value;
return start !== null && end !== null && start < end
? null
: { range: true };
};@Component({
selector: 'range',
template: `
<form [formGroup]="form">
<input formControlName="rangeStart" placeholder="Range start" type="number">
<input formControlName="rangeEnd" placeholder="Range end" type="number">
</form>
<div> Valid: {{ form.valid ? '๐' : '๐' }} </div>
`
})
export class AppComponent {
form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
rangeStart: [null, Validators.required],
rangeEnd: [null, Validators.required]
}, { validator: MyAwesomeRangeValidator });
}
}
If you like the content โฆ please clap ๐ ๐ ๐