I have a Reactive Form in an Angular 21 application, and the form requires an array of a certain sub-form. (In my case the sub-form(s) define "filters" on a search.) The documentation on angular.dev only shows a case of FormArray where the array holds multiples of a single FormControl. I am struggling with a FormArray containing multiples of a FormGroup type. Here's my form in the .ts file:
myQyeryForm = this.formBuilder.group({
queryTable: [0],
filters: this.formBuilder.array([this.formBuilder.group({operator: ['']})]),
});
Note that the nested FormGroup is intended to have more than one field, I'm keeping it simple for clarity.
And here's my template, so far:
<div class="filtersbox">
@for(filter of filters.controls; track $index; let i=$index) {
<div [formGroupName]="i">
<mat-form-field>
<mat-label>Operator</mat-label>
<input matInput id="operator-{{i}}" type="text" formControlName="operator" />
</mat-form-field>
</div>
}
</div>
When I open the site in the browser, I get the following errors popping up in the dev console:
ERROR TypeError: Cannot read properties of null (reading 'addFormGroup') at i.ngOnInit` (once)
ERROR TypeError: Cannot read properties of null (reading 'addControl') at i._setUpControl` (once)
ERROR TypeError: Cannot read properties of undefined (reading 'controlType') at i._initializeControl` (repeating indefinitely)
There are old questions on the site about this problem (eg. this one and that one) but unfortunately, Angular has changed how reactive forms with FormArrays are implemented, as well as replacing ngFor with a new way of doing loops, and those solutions don't help. In addition, I'm using Angular Material for the form fields, which may also be a problem.
When you manage a FormArray inside a FormGroup using [FormGroupName]="i"you should indicate:
@for the formGroupName <form [formGroup]="myQyeryForm">
<div formArrayName="filters">
@for(filter of filters.controls; track $index; let i=$index) {
<div [formGroupName]="i">
<label>Operator</label>
<input id="operator-{{i}}" type="text" formControlName="operator" />
</div>
}
</div>
</form>
From Angular 20 and above you can also, when define the "getter" indicate the kind of formArray, (see the FormArray<FormGroup>)
get filters() {
return this.myQyeryForm.controls['filters'] as FormArray<FormGroup>;
}
So you can indicate the [formGroup]='filters' and forget use formArrayName and formGroupName (really you can even not use the [FormGroup]='myQueryForm')
<form [formGroup]="myQyeryForm">
@for(filter of filters.controls; track $index) {
<div [formGroup]="filter"> //<--see the [formGroup]
<label>Operator</label>
<input id="operator-{{$index}}" type="text" formControlName="operator" />
</div>
}
</form>
NOTE: in this stackblitz I made an example whit the last form also using the constructor of FormArray, FormGroup and FormControl instead of use formBuilder