Search code examples
angularangular-reactive-formsangular-formsformarrayangular18

Validator in nested FormGroup of FormArray does not work


I have an easy setup with Angular typed forms.
In the example I built a FormGroup with two FormControls:

  1. A normal text field with required Validators
  2. A form Array that contains a FormGroup with a single FormControl that is again just a text.

I also have two buttons where new FormGroups can be added/removed from the FormArray:

export class AppComponent {
  formGroup = this.fb.nonNullable.group({
    Name: ['', Validators.required],
    AppAssignments: this.fb.array(
      [] as FormGroup<{
        Name: FormControl<string | null>;
      }>[]
    ),
  });

  constructor(
    private fb: FormBuilder,
    public changeDetectorRef: ChangeDetectorRef
  ) {}

  public addAppAssignment() {
    this.formGroup.controls.AppAssignments.controls.push(
      this.fb.group({
        Name: new FormControl<string | null>(null, Validators.required),
      })
    );
    this.formGroup.updateValueAndValidity();
    this.changeDetectorRef.detectChanges();
  }
  public removeAppAssignment() {
    if (this.formGroup.controls.AppAssignments.length > 0) {
      this.formGroup.controls.AppAssignments.removeAt(
        this.formGroup.controls.AppAssignments.length - 1
      );
    }
  }
}

I am expecting the "Submit"-Button on the bottom of the form to be disabled/enabled depending on the status of the FormGroup.

However, adding Elements to the FormArray does not invalidate the FormControl.

When I debug the whole thing, I can see that after adding new FormControls to the FormArray, the FormArray is "VALID", even though some of the FormGroups inside are not valid anymore.

How can this be?

This Stackblitz demonstrates my problem.


Solution

  • You need to use the push method on the control AppAssignments, that is what caused the bug.

    Cause:

    The push of FormArray might have additional logic added to it, for attaching controls, the AppAssignments.controls is just a regular array, whose push method, does not have this extra code, which might have caused the bug

    Before:

    this.formGroup.controls.AppAssignments.controls.push(
      ...
    );
    

    After:

    this.formGroup.controls.AppAssignments.push(
      ...
    );
    

    Full Code:

    import { ChangeDetectorRef, Component } from '@angular/core';
    import {
      FormArray,
      FormBuilder,
      FormControl,
      FormGroup,
      Validators,
    } from '@angular/forms';
    
    interface FormModel {
      title: FormControl<string | null>;
      name: FormGroup<{
        firstName: FormControl<string | null>;
        lastName: FormControl<string | null>;
      }>;
      interest: FormArray<FormControl<string | null>>;
    }
    
    @Component({
      selector: 'my-app',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
    })
    export class AppComponent {
      formGroup = this.fb.nonNullable.group({
        Name: ['', Validators.required],
        AppAssignments: this.fb.array(
          [] as FormGroup<{
            Name: FormControl<string | null>;
          }>[]
        ),
      });
    
      constructor(
        private fb: FormBuilder,
        public changeDetectorRef: ChangeDetectorRef
      ) {}
    
      public addAppAssignment() {
        this.formGroup.controls.AppAssignments.controls.push(
          this.fb.group({
            Name: new FormControl<string | null>(null, Validators.required),
          })
        );
        this.formGroup.updateValueAndValidity();
        this.changeDetectorRef.detectChanges();
      }
      public removeAppAssignment() {
        if (this.formGroup.controls.AppAssignments.length > 0) {
          this.formGroup.controls.AppAssignments.removeAt(
            this.formGroup.controls.AppAssignments.length - 1
          );
        }
      }
    }
    

    Stackblitz Demo