Search code examples
angularangular-validation

Angular custom validator breaks when providing FormArray with anything other than empty array


I have a custom validator that I'm using with my FormArray, the validator works fine as long as the input to the FormArray is []. However if I provide the FormArray with a string[] or FormControl[] the validator will break the whole angular app and cause infinite looping errors.

I'm really not sure what is causing this to occur. I need to be able to instantiate the FormArray with elements so I need this to work.

At first I thought the problem was being caused by the fact I was passing a string[] to the FormArray instead of FormControl[], but even after mapping the array to an array of FormControls I get the same problem.

this.alertData.alertContacts is simply an array of email strings.

FormGroup instantiation - Works

this.form = this.formBuilder.group({
      alertContacts: this.formBuilder.array([], [Validators.required, alertContactsArrayValidator()]),
    });

FormGroup instantiation - Broken

this.form = this.formBuilder.group({
      alertContacts: this.formBuilder.array(this.alertData.alertContacts, [Validators.required, alertContactsArrayValidator()]),
    });

FormGroup instantiation - Broken

this.form = this.formBuilder.group({
      alertContacts: this.formBuilder.array(this.alertData.alertContacts.map(x => new FormControl(x)), [Validators.required, alertContactsArrayValidator()]),
    });

Custom Validator

export function alertContactsArrayValidator(): ValidatorFn {
  return (c: AbstractControl): { [key: string]: any } | null => {
    let formArray = <FormArray>c;
    console.log(formArray);
    let hasEmailErrors = false;
    let formControls = formArray.controls;
    console.log(formControls);
    formControls.forEach(control => {
      let controlErrors: ValidationErrors = control.errors;
      console.log("Current control errors: ", controlErrors);
      Object.keys(controlErrors).forEach(key => {
        if (key === 'email') {
          hasEmailErrors = true;
          console.log("key has email");
        }
      });
    });
    return hasEmailErrors ? {'email': true} : null;
  };
}

Solution

  • you need check if control.errors is null.

    formControls.forEach(control => {
          let controlErrors: ValidationErrors = control.errors;
          if (controlErrors){ //<---this "if"
            Object.keys(controlErrors).forEach(key => {
              if (key === 'email') {
                hasEmailErrors = true;
                console.log("key has email");
              }
            });
          }
        });
    

    You can also make simpler

    formControls.forEach(control => {
       if (control.errors && control.errors.email){
            hasEmailErrors = true;
            console.log("key has email");
       }
    });
    

    even

    formControls.forEach(control => {
       hasEmailErrors = hasEmailErrors ||
          (control.errors && control.errors.email)
       }
    });
    

    NOTE: I think you want to make

    this.form = this.formBuilder.group({
          alertContacts: this.formBuilder.array(this.alertData.alertContacts
           .map(x => new FormControl(x,[Validators.required,Validators.email])),
                [alertContactsArrayValidator()]),
        });
    

    the FormsControls has required and email validator and the array the custom validator