How to identify and handle the invalid control within an Angular(v2 onwards) reactive form Form Array

When this.from.valid returns false, I utilize the following approach to identify the first invalid control and handle the error in my component. While this method works well for form groups without any form array, is there a way to identify 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;
        }

        // Function to get validation errors from form controls
        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 to beautify control names
        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:


        // Checking form validity and handling errors
        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;
        }
    

Answer №1

Consider the scenario where a FormArray is a FormArray of FormControls, in which case your code should look something like this:

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)
            }
        }
        ...

To handle this situation properly, you should modify your code as follows:

  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 a condition for when the FormArray is a FormArray of FormControls
          const obj = {};
          obj[key + '-' + i] = arrayControl;
          i++;
          errors = errors.concat(getFormValidationErrors(obj));
        }
      }
    }

You can view a demonstration of this implementation on StackBlitz.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Guidelines for declaring types in variable definitions in Typescript

In Typescript, you have multiple options to define a boolean variable such as: let a = true; let b: boolean = true; Given that true is already a boolean value, is it still typical to explicitly declare the variable type (like shown for b)? If yes, does ...

Can Angular's change detection be triggered by a change in @Input?

As of now, I am immersing myself in guides and tutorials on Angular's change detection. There are some statements that are quite perplexing to me. Therefore, I am seeking confirmation or clarification. The default Change Detection is activated "every ...

Using TypeScript to incorporate JS web assembly into your project

I have been attempting to incorporate wasm-clingo into my TypeScript React project. I tried creating my own .d.ts file for the project: // wasm-clingo.d.ts declare module 'wasm-clingo' { export const Module: any; } and importing it like this: ...

Achieving Full Screen Height in Angular Component

Trying to nest an angular component within another one that occupies the entire screen using height:100%; in the component's css file. I want to stack them and ensure each component takes up the full screen height, but setting height:100% doesn't ...

The Angular NgForm method form.resetForm does not seem to be resetting properly when dealing with arrays

I encountered an issue with my simple ngForm that was previously functioning well with string input and single select dropdowns. When I added a multi-select dropdown that introduces an array, I started facing a strange problem. Even after using form.formRe ...

Too late for the spinner

I have developed an Angular application that includes a table and buttons to switch between different data sources for the table. To enhance user experience, I want to display a spinner while switching data sources. However, I am facing an issue where th ...

Encountering an error message during the installation of 'ngx-toastr' within an Angular project

Whenever I attempt to install 'ngx-toastr', I encounter an error message. Additionally, my website is being created using Bootstrap. ERROR npm ERR! Could not resolve dependency: npm ERR! peer @angular/common@">=16.0.0-0" from <a ...

What are the steps to defining a static constant within a TypeScript class?

What is the best way to define a TypeScript static constant within a class so that it can be accessed without initializing the class instance? Below is an example of my class structure: export class CallTree{ public static readonly active = 1; .. ...

What is the best way to add prefixes to my SCSS style sheets?

After attempting to add prefixes to my scss files, I came across the autoprefixer tool. However, I discovered that it only supports CSS files. Is there a way to utilize autoprefixer with scss files? Here are the commands for Autoprefixer: npm install post ...

The Angular Material DatePicker fails to update in accordance with the Moment locale settings

Issue: Inconsistency with Angular Material 7 DatePicker and moment js integration Challenge: The datepickers fail to update their locale when the language of the web application is changed. Objective: I aim to have my date pickers automatically adjust to ...

Updating the FormControl value using the ControlValueAccessor

I have developed a directive specifically for case manipulation. The code I created successfully updates the visual value using _Renderer2, but I am facing an issue with formGroup.value. Even though the directive visually changes the value as expected, t ...

Tips for creating a seamless horizontal scrolling effect in Angular when hovering (automatically)

One of the components I'm working on features a gallery with an X axis orientation. <div class="gallery"> <div (mouseenter)="scrollTo('left', $event)" (mouseleave)="clearIntervalRepeater()" class="left"></div> < ...

Is there a way to identify the specific button that was clicked within an Angular Material dialog?

import {Component, Inject} from '@angular/core'; import {MdDialog, MdDialogRef, MD_DIALOG_DATA} from '@angular/material'; /** * @title Dialog Overview Example with Angular Material */ @Component({ selector: 'dialog-overview-ex ...

Can you provide a guide on setting up and utilizing mathlive within NuxtJS?

Can anyone assist me? I am trying to figure out why my code is not working or if I have implemented it incorrectly. I used npm i mathlive to obtain input. However, following the instructions for implementing nuxt plugins in the documentation has not yield ...

Attempting to access a specific JSON key using Observables

Apologies for the question, but I'm relatively new to Typescript and Ionic, and I find myself a bit lost on how to proceed. I have a JSON file containing 150 entries that follow a quite simple interface declaration: export interface ReverseWords { id ...

Simplified File Paths and Default Files

Currently, I am working with Next.js and TypeScript, setting up path aliases in my project without any issues. However, I'm facing a small difficulty when it comes to dealing with index.ts files within folders. My goal is to achieve something similar ...

Why is TypeScript resorting to using 'any' for specific prop type definitions in React?

Having some trouble with my props typing: export interface ITouchable { isDisabled?: boolean; margin?: Margin; height?: number; bgColor?: string; } The definition of Margin is as follows: type Margin = | { top?: number; bottom?: nu ...

ag-grid Server Side pagination function to enable independent setting of last row

Currently, I am utilizing ag-grid, Angular 7, and implementing a server-side datasource with pagination. In my API setup, I initiate two requests: the first request provides the total number of items in the table, while the second fetches the data for the ...

What is the reason behind TypeScript interfaces lacking support for index signatures compared to type aliases?

Having a type with a string index signature: declare var result: { [key: string]: number; }; Trying to assign an interface to the type results in an error: interface IData { a: number; b: number; } declare var data: IData; result = data; // Error: T ...

Connecting Multiple Relationships with Many-To-Many Matches

My database consists of the following entities: @Entity class User { @ManyToMany(type => Group) @JoinTable() groups: Group[]; } @Entity class MediaObject { @ManyToMany(type => Group) @JoinTable() groups: Group[]; } @Entity ...