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;
}
1

1 Answer 1

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

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for the reply. Getting this error Property 'controls' does not exist on type 'AbstractControl'
@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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.