0

this.from.valid returns false I am using this approach below to get the first invalid control and throwing the error accordingly in my component. This approach is working fine form groups without any form array.

Is there a way to find out the first invalid control of a Form Array

get-form-validation-errors.ts

import {
    AbstractControl,
    FormArray,
    FormGroup,
    ValidationErrors
} from '@angular/forms';
export interface AllValidationErrors {
    control_name: string;
    error_name: string;
    error_value: any;
    control_modified: string;
}
export interface FormGroupControls {
    [key: string]: AbstractControl;
}

export function getFormValidationErrors(
    controls: FormGroupControls
): AllValidationErrors[] {
    let errors: AllValidationErrors[] = [];
    Object.keys(controls).forEach((key) => {
        const control = controls[key];
        if (control instanceof FormGroup) {
            errors = errors.concat(getFormValidationErrors(control.controls));
            control.markAsTouched({
                onlySelf: true
            });
        } else if (control instanceof FormArray) {
            for (const arrayControl of control.controls) {
                if (arrayControl instanceof FormGroup) {
                    errors = errors.concat(
                        getFormValidationErrors(arrayControl.controls)
                    );
                }
            }
        }

        const controlErrors: ValidationErrors = controls[key].errors;
        if (controlErrors !== null) {
            Object.keys(controlErrors).forEach((keyError) => {
                errors.push({
                    control_name: key,
                    error_name: keyError,
                    control_modified: beautifyControl(key),
                    error_value: controlErrors[keyError]
                });
            });
        }
    });
    return errors;
}


function beautifyControl(key: string): string {
    let result: string[] = [];
    const splitters = ['-', '_'] as const;

    if (key.includes(splitters[0])) result = key.split(splitters[0]);
    else if (key.includes(splitters[1])) result = key.split(splitters[1]);
    else result = key.replace(/([a-z])([A-Z])/g, '$1 $2').split(' ');

    return [
        ...result.map((e: string, i: number) => e[0].toUpperCase() + e.slice(1))
    ].join(' ');
}

Using example:

if (!this.formValid()) {
  const error: AllValidationErrors = getFormValidationErrors(this.regForm.controls).shift();
  if (error) {
    let text;
    switch (error.error_name) {
      case 'required': text = `${error.control_name} is required!`; break;
      case 'pattern': text = `${error.control_name} has wrong pattern!`; break;
      case 'email': text = `${error.control_name} has wrong email format!`; break;
      case 'minlength': text = `${error.control_name} has wrong length! Required length: ${error.error_value.requiredLength}`; break;
      case 'areEqual': text = `${error.control_name} must be equal!`; break;
      default: text = `${error.control_name}: ${error.error_name}: ${error.error_value}`;
    }
    this.error = text;
  }
  return;
}
RAHUL KUNDU
  • 745
  • 2
  • 12
  • 33

1 Answers1

1

I feel that you need take account when a FormArray is a FormArray of FormControls, so should be like

Object.keys(controls).forEach((key) => {
        const control = controls[key];
        if (control instanceof FormGroup) {
            errors = errors.concat(getFormValidationErrors(control.controls));
            control.markAsTouched({
                onlySelf: true
            });
        } else if (control instanceof FormArray) {
            for (const arrayControl of control.controls) {
                    //..call to your function directly..
                    errors = errors.concat(
                        getFormValidationErrors(arrayControl.controls)
            }
        }
        ...

so you need

  Object.keys(controls).forEach(key => {
    const control = controls[key];
    if (control instanceof FormGroup) {
        ...
    } else if (control instanceof FormArray) {
      let i: number = 0;
      for (const arrayControl of control.controls) {
        if (arrayControl instanceof FormGroup) {
          ...
        } else {  //add the case if the FormArray is a FormArray
                  //of a FormControls
          const obj = {};
          obj[key + '-' + i] = arrayControl;
          i++;
          errors = errors.concat(getFormValidationErrors(obj));
        }
      }
    }

I wrote a fool and ugly stackblitz

Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • Thank you for the reply. Getting this error `Property 'controls' does not exist on type 'AbstractControl'` – RAHUL KUNDU Jul 25 '21 at 17:39
  • @RAHULKUNDU, sorry, I went from another SO and read so quickly your code :(. The answer is that you need "control" the case of a FormArray of FormControls, see the updated answer – Eliseo Jul 26 '21 at 16:33