0

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!

1 Answer 1

2
  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

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for your quick response. Great answer, concise, and effective. However, I would appreciate it if you could provide more information on categoryFormGroup(). I have a basic understanding, but clarification would help me confirm my understanding. Also, can you please explain why we do not need the [formGroupName]="i" attribute? Thank you.
Hi, as your Input expects for value with FormGroup type, the form control returns AbstractControl, so you need to cast it and it can be done in the component side. why we do not need the [formGroupName]="i" attribute, as by adding this it will result in form array with nested groups: form Array -> form group -> form group, which I believe this is not what you want and you will get the run-time error for form Array -> 0 (form group index) -> (form group) doesn't exist based on your form structure.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.