Reputation: 1569
The migration from Angular 10 to Angular 15 has caused the following error on the console:
ERROR Error: Cannot find control with path: 'mappedHeaders -> 0 -> value'
example.ts
headerArray: FormArray;
formGroup: FormGroup;
constructor(private fb: FormBuilder) {
this.headerArray = this.fb.array([]);
this.formGroup = this.fb.group({
mappedHeaders: this.headerArray
});
}
printing the structure of this.headerArray on the console post initilization:
printing the structure of this.formGroup on the console after pushing the values:
example.html:
<div [formGroup]="formGroup">
<table>
<tbody formArrayName="mappedHeaders">
<ng-container *ngFor="let header of accountHeaders; index as i">
<tr>
<td>{{i}}</td>
<td [formGroupName]="i">
<select class="ui fluid clearable selection search dropdown column-mapping-dropdown" formControlName="value">
<option *ngFor="let header of fileHeaders" [value]="header">{{ header }}</option>
</select>
</td>
</tr>
</ng-container>
</tbody>
</table>
</div>
the accountHeaders
is an array of strings like ['one', 'two', 'three',...'etc']
The above code works perfectly as expected in Angular 10. I have checked other similar issues but none could address this.
Update The controls are pushed into array post a network call:
const mapped = result['mappedHeaders'];
for (const accountHeader of this.accountHeaders.sort()) {
this.headerArray.push(this.fb.control({
key: accountHeader,
value: mapped[accountHeader]
}));
}
mapped
is a map of key value like ['one': 'one', 'two': 'three']
Upvotes: 1
Views: 530
Reputation: 1569
combining the answers given by @Naren and @Eliseo, the below modification to the code resolved the issue.
1 - The iteration must happen on the formArray controls:
get formArrayControls() {
const formArray = this.formGroup.get('mappedHeaders') as FormArray;
return formArray.controls;
}
and
<ng-container *ngFor="let header of formArrayControls; index as i">
2 - I was pushing formControls into mappedHeaders array instead of formGroups. Therefore, I created a method that adds the elements to the array:
addToFormArray(key: string, value: string) {
let formArray = this.formGroup.get('mappedHeaders') as FormArray;
formArray.push(this.fb.group({
key: [key],
value: [value]
}));
}
and call the function as follows:
for (const accountHeader of this.accountHeaders.sort()) {
this.addToFormArray(accountHeader, mapped[accountHeader]);
}
Upvotes: 0
Reputation: 57981
NOTE: The question are perfectly response by Naren, this is only a brief note related to make a FormArray of FormControls, and remember: NEVER loop a formArray else over formArray.controls
A FormArray can be a formArray of FormGroups or a FormArray of FormsControls. I feel strange when someone use a formArray of FormGroups when it's only necessary a formArray of FormControls.
The value of a FormArray of FormGroups is an array of object, e.g. [{value:..},{value:...}...]
. The value of a FormArray of FormControls is an array of elements, e.g. [value1,value2,...]
Always we have a FormArray in a FormGroup, use a getter to get the formArray
accountHeades=['one', 'two', 'three']
get headerArray()
{
return this.formGroup.get('mappedHeaders') as FormArray
}
ngOnInit(){
this.formGroup = this.fb.group({
mappedHeaders: this.headerArray
});
this.headerArray=this.accountHeaders.map(x=>fb.control(x))
}
See how loop
<!--the formGroup directive can be use in the own table tag-->
<table [formGroup]="formGroup">
<tbody formArrayName="mappedHeaders">
<!--you loop over "formArray".controls
idem of formGroup, you can use the own tr to add the *ngFor
-->
<tr *ngFor="let header of headerArray.controls; index as i">
<td>{{i}}</td>
<td>
<!--a formArray of formControls you use formControlName
but not formGroupName -->
<select [formControlName]="i">
<option *ngFor="let header of fileHeaders" [value]="header">
{{ header }}
</option>
</select>
</td>
</tr>
</tbody>
</table>
Upvotes: 1
Reputation: 57521
This error occurs when there are no formGroup inside the form array, but we are running the for loop using the data object, which has values, but the form array is not having the corresponding form group.
We can just run a for loop over the data object and create the form groups, also we should loop through the controls instead of the data object, which will eliminate this error, altogether!
import { CommonModule } from '@angular/common';
import { Component, inject } from '@angular/core';
import {
ReactiveFormsModule,
FormBuilder,
FormArray,
FormGroup,
FormControl,
} from '@angular/forms';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
@Component({
selector: 'app-root',
standalone: true,
imports: [ReactiveFormsModule, CommonModule],
template: `
<div [formGroup]="formGroup">
<table>
<tbody formArrayName="mappedHeaders">
<!-- changed below -->
<ng-container *ngFor="let header of formArrayControls; index as i">
<tr>
<td>{{i}}</td>
<td [formGroupName]="i">
<select class="ui fluid clearable selection search dropdown column-mapping-dropdown" formControlName="value">
<option *ngFor="let header of fileHeaders" [value]="header">{{ header }}</option>
</select>
</td>
</tr>
</ng-container>
</tbody>
</table>
</div>
{{formGroup.value | json}}
`,
})
export class App {
fb = inject(FormBuilder);
headerArray = this.fb.array([]);
formGroup = this.fb.group({
mappedHeaders: this.headerArray,
});
accountHeaders = ['one', 'two', 'three'];
fileHeaders = [1, 2, 3];
get formArrayControls() {
const formArray = this.formGroup.get('mappedHeaders') as FormArray;
return formArray.controls;
}
ngOnInit() {
if (this.accountHeaders?.length) {
const formArray = this.formGroup.get('mappedHeaders') as FormArray;
this.accountHeaders.forEach((item: string) => {
const group = new FormGroup({ value: new FormControl(item) });
formArray.push(group);
});
}
}
}
bootstrapApplication(App);
Upvotes: 1