Reputation: 5266
I need to have a validation on a form in order to make the user fill at least one of two fields
I've tried with a custom validator like this:
export function conditionalValidator(
predicate: () => boolean,
validator: ValidatorFn
): ValidatorFn {
return formControl => {
if (!formControl.parent) {
return null;
}
let error = null;
if (predicate()) {
error = validator(formControl);
}
return error;
};
}
and then in my form
this.myForm= this.fb.group({
field1: ['', conditionalValidator(() => !(this.myForm.get('field1').value || this.myForm.get('field2').value), Validators.required)],
field2: ['', conditionalValidator(() => !(this.myForm.get('field1').value || this.myForm.get('field2').value), Validators.required)],
});
this.myForm.get('field1').valueChanges.subscribe(_ => {
this.myForm.updateValueAndValidity();
});
this.myForm.get('field2').valueChanges.subscribe(_ => {
this.myForm.updateValueAndValidity();
});
but I don't know why it doesn't work
I also tried with a more "handcraft" way (stackblitz link here) but with no luck either
Upvotes: 0
Views: 6513
Reputation: 57909
as mbojko say you can makes a custom validator over the whole formGroup. This makes that the validator is executed each change in the form. If only has this two fields is the optima solution.
But you can use also a custom validator over one field, the problem is that only execute the validator when change the form control but first we are going to see the validator
export function conditionalValidator(field:string): ValidatorFn {
return (formControl) => {
if (!formControl.parent) {
return null;
}
return formControl.value || formControl.parent.get(field).value?
null: {error:"this field or "+field+" is required"}
};
}
And you use like
this.myForm = this.fb.group({
myField: ["",conditionalValidator("otherField")],
otherField: ["",conditionalValidator("myField")],
});
Well, As I say, the problem when we makes a custom form control to a control that relay to another is that we need check when the other control has changed. You makes subscribing to valueChanges of the control. You can use merge
rxjs operator to have an unique subscribe
merge(this.myForm.get("otherField").valueChanges,
this.myForm.get("myField").valueChanges)
.subscribe(_ => {
this.myForm.get("otherField").updateValueAndValidity({emitEvent:false});
this.myForm.get("myField").updateValueAndValidity({emitEvent:false})
});
Your forked stackblitz
Update well, we know that the validator is executed each time ours controls changes, so why don't take account this and makes a updateValueAndValidity of the other field in this function?
Well, we need executed only if is valid and there're an error or is is invalid and has no error, else we get a recursive error
export function conditionalValidator(field: string): ValidatorFn {
return formControl => {
if (!formControl.parent) {
return null;
}
const otherControl = formControl.parent.get(field);
const error =
formControl.value || otherControl.value
? null
: { error: "this field or " + field + " is required" };
if ((error && otherControl.valid) || (!error && otherControl.invalid)) {
setTimeout(() => {
otherControl.updateValueAndValidity({ emitEvent: false });
});
}
return error;
};
}
So, we needn't subscribe
See a new stackblitz
Upvotes: 4
Reputation: 14669
That's what group validators (not field validators) are for.
this.myForm= this.fb.group({
field1: ['', Validators.required],
field2: ['', Validators.required],
}, {
validators: [/* put your multi-field validator here */],
}});
Upvotes: 0