Issue with Formgroup in Angular Reactive Form - Validation not functioning as expected

I am working with a form group which is defined below:

get createItem(): FormGroup {
    return this.formBuilder.group({
        name: ['', Validators.required],
        email: ['', Validators.required],
        mobile: ['', Validators.required],
    });
}

This form group is used to generate fields in the following structure:

<form [formGroup]="dynamicFormGroup" (ngSubmit)="onSubmit()">


    <div class="row" formArrayName="address" *ngFor="let fields of AddressInfo.controls; let i = index">
        <ng-container [formGroupName]="i">

            <div class="form-group col-md-12">
                <div class="each_content">
                    <label class="labelTitle">Name <span class="validation-error">*</span></label>
                    <input type="text" class="form-control height-reset" placeholder="Enter Name" name="name" formControlName="name" />

                    <div
                            *ngIf="dynamicFormGroup.get('name').invalid && (dynamicFormGroup.get('name').dirty || dynamicFormGroup.get('name').touched)">
                        <small class="validation-error">Please provide a title.</small>
                    </div>

Despite implementing the above code, the validation feature is not working as expected. Could anyone suggest a solution? Thank you.

Answer №1

To avoid seeing the error message, certain conditions must be met:

  • The value of the control is incorrect.
  • The control is either in a dirty or touched state.

If you submit the form without interacting with the controls that require validation, the second condition will not be satisfied.

Therefore, before submitting the form, ensure that the form controls are marked as dirty or touched. You can achieve this by using the markAllAsTouched method from the root FormGroup to mark all controls as touched.

onSubmit() {
  this.dynamicFormGroup.markAllAsTouched();

  ...
}

In relation to the comment, the name control does not exist in the address FormArray. It is unclear why the validation error message template is placed there.

I am highlighting one of the controls within the address FormArray: "streetAddress". To access the streetAddress control, you must provide the index of the address FormArray as shown below:

addressFormGroup(i: number) {
  return this.AddressInfo.controls[`${i}`] as FormGroup;
}
<div
  *ngIf="addressFormGroup(i).get('streetAddress')?.invalid && (addressFormGroup(i).get('streetAddress')?.dirty || addressFormGroup(i).get('streetAddress')?.touched)"
>
  <small class="validation-error"
    >Please provide a Street Address.</small
  >
</div>

View the demonstration

Answer №2

Important Update: I neglected to mention in the .css that it should include 'input.ng-invalid.ng-touched'. If 'input' is not specified, it will not function correctly.

You have two options:

dynamicFormGroup.get('address.'+i+'.name')

or

AddressInfo.at(i).get('name')

To access the formControl inside the FormArray, I recommend another approach.

Angular marks an invalid FormControl with the classes ng-invalid ng-touched and ng-dirty, and ng-submit in a form. You can utilize this by using the subsequent sibling combinator ~

If you do something like this:

<form [formGroup]="dynamicFormGroup" (ngSubmit)="onSubmit()" >
  <div formArrayName="address">
    <div class="row" *ngFor="let fields of AddressInfo.controls;let i=index" [formGroupName]="i">
       <div class="form-group col-md-12">
          <div class="each_content">
              <input type="text" class="form-control height-reset" placeholder="Enter Name" name="name" formControlName="name" />
              <label class="labelTitle">Name <span class="validation-error">*</span></label>

              <div ><small class="validation-error">Please provide a title.</small></div>
          </div>
       </div>
</div>
</div>
  <button>submit</button>
</form>  

NOTE: I prefer using the loop in a ng-container inside a div FormArray

For the onSubmit function:

onSubmit(){
   if (!this.dynamicFromGroup.valid)
      this.dynamicFormGroup.markAllAsTouched();
   else
   {
      ...do something...
   }
}

You can use the following .css:

.labelTitle{
  float:left;
}
.validation-error
{
   display:none; //<--default display is none
   color:red;
}
.ng-submitted input.ng-invalid.ng-touched ~ div .validation-error
{
   display:inline-block; //<---display only if a 'brother' element is invalid
}
.ng-submitted input.ng-invalid.ng-touched ~ label 
{
   height:1.5rem;
}
.ng-submitted input.ng-invalid.ng-touched ~ label .validation-error
{
   display:inline-block;
}

It may seem a bit complex - I had to use label float:left because of the '*' symbol.

Check out the stackblitz example

Answer №3

I made some modifications to your StackBlitz project and implemented a new hasError function to check the validity of specific inputs. Please have a look and verify if the changes work as expected.

ngOnInit() {
    this.dynamicFormGroup = this.formBuilder.group({
      address: this.formBuilder.array([this.createItem()]),
    });
  }

  get addresses(): FormArray {
    return this.dynamicFormGroup.get('address') as FormArray;
  }

  hasError(index: number, controlName: string): boolean {
    const control = this.addresses.at(index).get(controlName);
    if (control) {
      return control.invalid && (control.dirty || control.touched);
    }
    return false;
  }

  addNewAddress(): void {
    this.addresses.push(this.createItem());
  }

  createItem(): FormGroup {
    return this.formBuilder.group({
      streetAddress: ['', Validators.required],
      city: ['', Validators.required],
      state: ['', Validators.required],
    });
  }

This is how the template should be structured:

<form [formGroup]="dynamicFormGroup" (ngSubmit)="onSubmit()">
    <div class="form-row" formArrayName="address">
      <ng-container *ngFor="let adress of addresses.controls; index as i">
        <div [formGroupName]="i">
          <div class="form-group col-md-3">
            <label for="password"><b>Street Address</b></label>
            <input
              type="text"
              class="form-control"
              placeholder="Street Address"
              name="SA"
              formControlName="streetAddress"
            />
            <div *ngIf="hasError(i, 'streetAddress')">
              <small class="validation-error">Please provide a title.</small>
            </div>
          </div>

          <!-- Other input fields -->

        </div>
      </ng-container>
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
  </form>

The issue with

dynamicFormGroup.get('name').invalid
in your code is resolved by correctly accessing the nested formGroup inside formArray using the index and formControlName parameters in the hasError function.

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

Identify all elements that include the designated text within an SVG element

I want to target all elements that have a specific text within an SVG tag. For example, you can use the following code snippet: [...document.querySelectorAll("*")].filter(e => e.childNodes && [...e.childNodes].find(n => n.nodeValue ...

Leveraging vuex in conjunction with typescript allows for efficient management of state in namespace modules,

I am currently integrating vuex with typescript and namespaces module in my project. Within this setup, I have two distinct modules: "UserProfile" and "Trips". So far, everything is functioning smoothly within the confines of each module. However, I have ...

Develop interactive web applications using Typescript

Having difficulty compiling and executing the project correctly in the browser. The "master" branch works fine, but I'm currently working on the "develop" branch. It's a basic web project with one HTML file loading one TS/JS file that includes i ...

Utilize TypeScript to retrieve the enumeration values as a parameter through a method that employs a generic enum type

Is there a way to retrieve all values of an Enum (specified as a parameter or generic) and return them in a list? Additionally, if the user has a specific role, I only need to retrieve certain Enum values provided as a parameter. I had the idea of groupin ...

Detecting the upcoming click on the document using @HostListener in Angular 4 to dynamically generate a click listener

I'm currently working on a directive that toggles its state when clicked. The desired behavior is for the state to be deactivated if the user clicks anywhere else on the page while it is active. To achieve this, I attempted to use the Renderer2 liste ...

Struggling to apply filtering on an array containing multiple objects within Ionic 2

I have successfully implemented filtering on the following array: this.items = [ 'Amsterdam', 'Bogota', 'India' ]; The code used in my list.ts file is as follows: if (val && val.trim() != '') { thi ...

How to integrate custom plugins like Appsee or UXCAM into your Ionic 2 application

Struggling to integrate either Appsee or UXcam into my project. I attempted to import the plugin like this, but it didn't work: // import { Appsee } from '@ionic-native/appsee'; I read somewhere that you need to declare the variable for Ty ...

Mismatched data types caused by immutability

I'm having trouble with my object that has a middleware property and the types aren't working as expected. The error message is stating that the two middlewares are incompatible because one of them is set to readonly. Does anyone know how I can r ...

Tips for effectively handling notifications using a single state management system

This React project showcases the Notification System demo provided by Mantine. It includes several function components utilizing notification. const A = () => { useEffect(() => { notifications.show({ // config }); }, []); retur ...

Transforming JSON data into XML using Angular 7

It turns out the xml2js npm package I was using doesn't support converting JSON to XML, which is exactly what I need for my API that communicates with an application only accepting XML format. In my service file shipment.service.ts import { Injecta ...

Different Angular 2 components are resolved by routes

Consider this scenario: Upon navigating to the URL /product/123, the goal is to display the ProductComponent. This is how it's currently configured: RouterModule.forRoot([ { path: 'product/:productId', ...

What steps do I need to follow to write this function in TypeScript?

I am encountering a problem when building the project where it shows errors in 2 functions. Can someone please assist me? The first error message is as follows: Argument of type 'IFilmCard[] | undefined' is not assignable to parameter of type &a ...

Angular 6 - Separate whole numbers and decimal values to style them differently in CSS

Is there a way in Angular to format a price so that the cents/decimal part appears as a superscript? What would be the most effective approach in Angular to achieve this based on the initial setup below? export class PriceComponent implements OnInit { ...

Optimal Method for Inserting Lengthy Data using Sequelize

I have a table containing 20 elements. Is there a more concise way to input data into Sequelize without listing each element individually like this: Sequelize.create({ elem1: req.body.eleme1, elem2: req.body.eleme2, elem3: req.body.eleme3, elem4: ...

What is the best way to forward all URLs to one central page?

I've recently started working with Angular and I'm currently developing a web app project using Angular 9. I could really use your help with this. My goal is to have a dynamic URL structure for the web app, such as https://www.myMainURL.com/***, ...

Prevent the Vue page from loading until the data has been fetched successfully

I'm in the process of finding a way to prevent my page from loading until my fetch task is completed. I'm facing some issues that need to be addressed: I have to re-fetch the data each time because I can't reuse the same data. (Changing vie ...

The code compilation of Typescript in a Dockerfile is not functioning as expected due to the error "Name 'process' cannot be found."

Here's the Dockerfile I'm working with: FROM node:latest WORKDIR /usr/src/app ENV NODE_ENV=production COPY package*.json . RUN npm install && npm i -g typescript COPY . . RUN tsc CMD [ "node", "./dist/index.js&qu ...

Strict type inference for the number data type in TypeScript

I am interested in inferring the number type within this function: type Options = { count: number }; function bar<C extends Options>(options: C): C['count'] extends 3 ? 'x' : 'y' {} bar({ count: 3 }) // x bar({ count: ...

How can you determine the number of times a particular digit appears around a specific position in a TypeScript array

(Utilizing Typescript for assistance) I am looking to determine the number of occurrences of the digit 2 surrounding a specific position within an array. The function takes in the position coordinates as parameters - param 1 for row and param 2 for column. ...

Why does the Amazon DynamoDB scan trigger an error by making two HTTP requests to the server?

My TypeScript React application is using DynamoDB services to store data, with a JavaScript middleware handling the database access operations. While developing locally, I successfully implemented the scan, put, and delete operations with no errors. Howeve ...