Niamatullah Bakhshi
Niamatullah Bakhshi

Reputation: 1569

Cannot find control with path error after migrating to Angular 15

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

Upvotes: 1

Views: 530

Answers (3)

Niamatullah Bakhshi
Niamatullah Bakhshi

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

Eliseo
Eliseo

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

Naren Murali
Naren Murali

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

Stackblitz Demo

Upvotes: 1

Related Questions