Search code examples
angularangular-reactive-formsformarrayangular17

Angular 17 - Error when creating reactive dynamic form array with child component


I'm trying to create a dynamic form array in Angular 17 with a child component handling part of the input. However, I'm encountering an error:

Type 'AbstractControl<any, any>' is missing the following properties from type 'FormGroup': controls, registerControl, addControl, removeControl, and 2 more.

Here's my code:

Parent component:

@Component({
  selector: 'app',
  template: `
  <form [formGroup]="categoryForm" (ngSubmit)="onSubmit()">
    <div formArrayName="categories" class="sections">
      <div *ngFor="let category of categories.controls; let i=index">

        <ng-container [formGroupName]="i">
          <!-- CHILD COMPONENT -->
          <app-category [formGroup]="category" [categories]="categories"  [index]="i"></app-category>

          <!-- ARRAY INPUTS -->
          <!-- <div style="border: 1px solid blue; padding: 10px; width: 800px; margin: 5px; background: pink;">
            <p><strong>Category : {{i+1}}</strong></p>
            Category ID :
            <input type="text" formControlName="categoryID" />
            Category Name:
            <input type="text" formControlName="categoryName" />

            <button type="button" class="remove" (click)="removeCategory(i)">Remove Category</button>
          </div> -->
        </ng-container>
      </div>
    </div>
    <button class="btn" type="button" (click)="addCategory()">Add Category</button>
    <button class="btn" type="submit">Submit</button>
  </form>

{{this.categoryForm.value | json}}
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: true,
  imports: [ReactiveFormsModule, JsonPipe, NgFor, CategoryComponent],
})
export class AppComponent {
  categoryForm!: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.categoryForm = this.fb.group({
      categories: this.fb.array([]),
    });
  }

  get categories(): FormArray {
    return this.categoryForm.get('categories') as FormArray;
  }

  addCategory() {
    this.categories.push(
      this.fb.group({
        categoryID: '',
        categoryName: '',
        sections: this.fb.array([]),
      })
    );
  }

  removeCategory(catIndex: number) {
    this.categories.removeAt(catIndex);
  }

  onSubmit() {
    console.log(this.categoryForm.value);
  }
}

Child component:

@Component({
  selector: 'app-category',
  template: `
  <form [formGroup]="formGroup">
    <div style="border: 1px solid blue; padding: 10px; width: 800px; margin: 5px; background: pink;">
      <p><strong>Category : {{index+1}}</strong></p>
      Category ID :
      <input type="text" formControlName="categoryID" />
      Category Name:
      <input type="text" formControlName="categoryName" />

      <button type="button" class="remove" (click)="removeCategory()">Remove Category</button>
    </div>
  </form>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: true,
  imports: [ReactiveFormsModule, NgFor],
})
export class CategoryComponent {
  @Input() categories!: FormArray;
  @Input() formGroup!: FormGroup;
  @Input() index!: number;

  removeCategory() {
    this.categories.removeAt(this.index);
  }
}

I've created a form array in the parent component and tried to loop through it using *ngFor, passing each form group to the child component. The child component receives the form group via @Input() and handles part of the input fields.

Can someone help me understand why this error is occurring and how to resolve it? Thank you!


Solution

    1. You need to cast the AbstractControl to FormGroup.
    2. Remove [formGroupName]="i" from <ng-container> as it is not needed.
    <ng-container>
        <!-- CHILD COMPONENT -->
        <app-category [formGroup]="categoryFormGroup(i)" [categories]="categories"  [index]="i"></app-category>
    </ng-container>
    
    categoryFormGroup(i: number) {
      return this.categories.get(`${i}`) as FormGroup;
    }
    

    Demo @ StackBlitz