Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to implement Angular cross field validation

I am trying to figure out the best way to implement cross field validation in Angular.

For example, I have a select field that makes another field mandatory.

I want to be able to:

  • Change the border color of the field if it is invalid
  • Display a * in front of the field whenever it becomes mandatory
  • Display a specific error message that explains what validation rule is broken.

So far, I came up with three solutions but they don't feel so convincing to me.

  • Listen to select field changes and update second field's validators.
  • Listen to both fields changes and manually perform setErrors
  • Lift validation to formGroup (which can feel extremely cumbersome since, validation state is now stored in formGroup and not directly available in formControl).

Here is a Stackblitz implementation that demos my investigations.

like image 283
Heyjojo Avatar asked Oct 18 '25 17:10

Heyjojo


1 Answers

UPDATE - ANOTHER APPROACH See this SO

UPDATE - A BETTER APPROACH

Create the customValidator over the form and use the validator to use setError to the control required. Using setError, make that Angular adds ng-invalid for us, ad we needn't subscribe to value change. See:

form: FormGroup = new FormGroup(
  {
    input1: new FormControl('optional'),
    input2: new FormControl(null),
  },
  { validators: this.customValidatorForm() },
);

customValidatorForm() {
  return (form: FormGroup) => {
    const error =
      form.get('input1').value != 'optional' && !form.get('input2').value
        ? { required: true }
        : null;
    form.get('input2').setErrors(error); //<--see the setErrors
    return error;
  };
}

See stackblitz

OLD ANSWER

Just use a customValidator like:

form: FormGroup = new FormGroup({
  input1: new FormControl('optional'),
  input2: new FormControl(null, this.customValidator()),
});

customValidator() {
  return (control: any) => {
    if (!control.parent) return null;

    let mandatory = control.parent.get('input1').value;
    return mandatory != 'optional' && !control.value ? { required: true } : null;
  };
}

Another option for not ask for control.parent it's use .bind(this). This allow us have inside the validator to all the variables of our component, and of course access to this.form:

form: FormGroup = new FormGroup({
  input1: new FormControl('optional'),
  input2: new FormControl(null, this.customValidator().bind(this)), //<--bind(this)
});

customValidatorBind() {
  return (control: any) => {
    if (!this.form) return null;

    let mandatory = this.form.get('input1').value;
    return mandatory != 'optional' && !control.value ? { required: true } : null;
  };
}

Well, as we want that when change input1 input2 was checked, you need use, after create the form subscribe to valueChanges:

this.form.get('input1').valueChanges.subscribe(() => {
  this.form.get('input2').updateValueAndValidity();
});
like image 56
Eliseo Avatar answered Oct 20 '25 07:10

Eliseo