I have these fields:
field1
field2
field3
field4
this.form = this.formbuilder.group({
'field1' = ['', [<control-specific-validations>]],
'field2' = ['', [<control-specific-validations>]]
}, { validator: isField1GreaterThanField2Validator}
});
More validations I need:
- field3.value must be greater than field4.value
- field3.value must be greater than field2.value + 1
- field4.value must be less than field1.value
How do you integrate these new validation requirements into the constructed form?
What I do NOT want to do is to setup a
formControl.valueChanges.subscribe(value => { });
for each field and then have many if/else there.
Then I could remove the whole reactive forms module, use 2way-databinding and render a error string in the ui when the validation expression is true.
You can use a custom validator to do this. https://angular.io/guide/form-validation#custom-validators
Here is a working example of what you want: https://stackblitz.com/edit/isgreaterthanotherfield-custom-validator
The validator function itself looks like this:
greaterThan(field: string): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} => {
const group = control.parent;
const fieldToCompare = group.get(field);
const isLessThan = Number(fieldToCompare.value) > Number(control.value);
return isLessThan ? {'lessThan': {value: control.value}} : null;
}
}
I am using the parent
property on the control to access the other field.
Note that you cannot set that validator in the form initialization, since the field you are basing it on is not yet defined.
this.myForm = this.fb.group({
field1: 0,
field2: 0
});
this.myForm.get('field2').setValidators(this.greaterThan('field1'));
Update: I took it a step further and implemented a custom validator which accepts a predicate function so you can use the same validator for all your comparisons.
See it in action here: https://stackblitz.com/edit/comparison-custom-validator
It uses the same approach as above, but is a bit more flexible since you can pass in any comparison. There are some edge cases the example doesn't consider, like passing a form field name that doesn't exist, if the form field you pass does not actually use numbers / the types don't match up, etc, but I believe that is outside the scope of the question.
This was an interesting question and I enjoyed working on it.
For quick reference, here is what the entire component looks like with the flexible custom validator:
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
myForm: FormGroup;
field2HasError: boolean;
field3HasError: boolean;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.myForm = this.fb.group({
field1: 0,
field2: 0,
field3: 0,
field4: 0
});
const field1MustBeGreaterThanField2 =
this.comparison('field1', (field2Val, field1Val) => {
return Number(field2Val) < Number(field1Val);
});
const field3MustBeGreaterThanField2Plus1 =
this.comparison('field2', (field3Val, field2Val) => {
return Number(field3Val) > (Number(field2Val) + 1);
});
this.myForm.get('field2').setValidators(field1MustBeGreaterThanField2);
this.myForm.get('field3').setValidators(field3MustBeGreaterThanField2Plus1);
this.myForm.get('field2').valueChanges.subscribe(() => {
this.field2HasError = this.myForm.get('field2').hasError('comparison');
});
this.myForm.get('field3').valueChanges.subscribe(() => {
this.field3HasError = this.myForm.get('field3').hasError('comparison');
});
}
comparison(field: string, predicate: (fieldVal, fieldToCompareVal) => boolean): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} => {
const group = control.parent;
const fieldToCompare = group.get(field);
console.log('fieldToCompare.value', fieldToCompare.value);
console.log('field.value', control.value);
const valid = predicate(control.value, fieldToCompare.value);
return valid ? null : {'comparison': {value: control.value}};
}
}
}
//your .ts
ngOnInit() {
this.myForm = this.fb.group({
field1: 0,
field2: 0,
field3: 0,
}, { validator: this.customValidator }); //a unique validator
}
customValidator(formGroup: FormGroup) {
let errors:any={};
let i:number=0;
let valueBefore:any=null;
Object.keys(formGroup.controls).forEach(field => {
const control = formGroup.get(field);
if (valueBefore)
{
if (parseInt(control.value)<valueBefore)
{
let newError:any={['errorLess'+i]:true} //create an object like,e.g. {errorLess1:true}
errors={...errors,...newError}; //Concat errors
}
}
valueBefore=parseInt(control.value);
i++;
});
if (errors)
return errors;
}
}
The .html like
<form [formGroup]="myForm">
field1:
<input formControlName="field1">
<br> field2:
<input formControlName="field2">
<div *ngIf="myForm.hasError('errorLess1')">Field 2 less that field 1</div>
<br> field3:
<input formControlName="field3">
<div *ngIf="myForm.hasError('errorLess2')">Field 3 less that field 2</div>
</form>
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