Doc
Doc

Reputation: 5266

Angular conditional required validator between two fields

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

Answers (2)

Eliseo
Eliseo

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

mbojko
mbojko

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

Related Questions