Angular Reactive Form - Validation is not working in formGroup

253 Views Asked by At

I have this form group defined

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

Generating fields like this

<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>

However, the validation is not working, any solution thanks.

3

There are 3 best solutions below

2
JB17 On

I edited your StackBlitz a bit and made some minor changes. Please check and see if it works for you. I introduced a hasError function, which takes the according input and checks the validity.

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],
    });
  }

The template would look like this:

<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 dynamicFormGroup.get('name').invalid in your code would not return anything, since you are trying to get a formGroup inside of the form, but you would need a specific formGroup inside of the nested formArray.
dynamicFormGroup --> adresses --> specific name input

By passing the index and the formControlName to the hasError function this should work.

0
Yong Shun On

The reason why the error is not shown as it must fulfill these conditions:

  • The control's value is invalid.
  • The control is either dirty or touched state.

When you submit the form without touching/inputting the form control(s) which has the validation, the second condition will not be fulfilled.

Hence, when you submit the form, you need to set the form control(s) as dirty or touched state. You can use the markAllAsTouched method from the root FormGroup to mark all the controls as touched.

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

  ...
}

Back to the part discussed in the comment, the name control doesn't exist in the FormGroup of address FormArray. So unsure why you place the validation error message template under there.

I demonstrate one of the controls that fall under the address FormArray: "streetAddress". To retrieve the streetAddress control, you need to provide the index of address FormArray as 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>

Demo @ StackBlitz

0
Eliseo On

Update I forgot indicate in .css is ìnput.ng-invalid.ng-touched. If you not indicate "input" not work

You can use

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

or

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

To "reach" the formControl inside the FormArray, but I suggest another approach

As Angular mark an invalid FormControl with the class ng-invalid ng-touched and ng-dirty in a formCotnrol and ng-submit in a form. you can take advantage of it using the subsequent sibling combinator ~

If you use some like

<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 like more use the loop in a ng-container inside a div FormArray

Where

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

You can use a .css like

.labelTitle{
  float:left;
}
.validation-error
{
   display:none; //<--see that by defect display is none
   color:red;
}
.ng-submitted input.ng-invalid.ng-touched ~ div .validation-error
{
   display:inline-block; //<---but where have a "brother" 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;
}

Really it's a bit complex -I need use label float:left because the "*"

a stackblitz