1

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:

form array first element

printing the structure of this.formGroup on the console after pushing the values: formGroup.controls

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']

2
  • Did you initialize the headerArray by adding the FormGroup based on accountHeaders array? If yes, can you show the code for initializing it? Otherwise, you may reproduce in StackBlitz. Thanks.
    – Yong Shun
    Commented May 1, 2024 at 7:17
  • @YongShun this code works perfectly in Angular 10. That means everything has been initilized. Commented May 1, 2024 at 7:19

3 Answers 3

1

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);

Stackblitz Demo

8
  • Thanks. Perhaps, I need to refactor more parts of the code because this much change didn't work. I got the idea though. The Stackblitz Demo doesn't work either. Commented May 1, 2024 at 7:43
  • @NiamatullahBakhshi You need to create a form group inside the form array, but you are having a form control inside the form array, that is why you are getting the error, please check stackblitz, it works fine I checked! Commented May 1, 2024 at 7:47
  • thanks. Did you notice the update part of the question? I am trying to accomodate your changes into my code. Getting so many errors. Commented May 2, 2024 at 5:43
  • @NiamatullahBakhshi please change it to this.headerArray.push(new FormGroup({ // key: accountHeader, value: new FormControl(mapped[accountHeader]), })); this will work! Like explained, the form array, should contain a formGroup that should contain the control with name: value! Commented May 2, 2024 at 5:47
  • each formControl has a key and value as shown above. Now when selecting an option, the first item in mappedHeaders changes from 0:value:key:"one" value:"two" to 0:value:"two" . The key is removed! Commented May 2, 2024 at 8:05
1

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>
2
  • thanks for the effort. This didn't work for me. This code works perfectly in Angular 10 though. Naren's answer seems closer but it still requires refactoring several parts of the code. I need to get it working with minimal change. Commented May 2, 2024 at 5:40
  • did you check the update part of the question? Commented May 2, 2024 at 5:41
0

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]);
  }

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.