Angular 2 Form "Cannot find control with path"
Asked Answered
I

7

61

I try to make a dynamic form (so you can limitless add items to a list), but somehow the content of my list is not getting send because it can't find the control with path:

Cannot find control with path: 'list_items -> list_item'

My component:

@Component({
  selector: 'app-list',
  templateUrl: './list.component.html',
  styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {

  listForm: FormGroup;

  constructor(private nodeService: NodeService, private messageService: MessageService, private fb: FormBuilder,
                private listService: ListService) { 
    this.listForm = this.fb.group({
      title: ['', [Validators.required, Validators.minLength(5)]],
      list_items: this.fb.array([
        this.initListItem(),
        ])
    });
  }


  initListItem() {
    return this.fb.group({
      list_item: ['']
    });
  }
  initListItemType() {
    return this.fb.group({
      list_item_type: ['']
    });
  }

  addListItem() {
    const control = <FormArray>this.listForm.controls['list_items'];
    control.push(this.initListItem());
  }

The Template (list.component.html):

<h2>Add List </h2>

<form [formGroup]="listForm" novalidate (ngSubmit)="addList(listForm)">
  <div class="form-group">
    <input type="text" class="form-control" formControlName="title" placeholder="Title">
  </div>
  
  <div formArrayName="list_items">
    <div *ngFor="let list_item of listForm.controls.list_items.controls; let i=index" class="panel panel-default">
      {{i + 1}}.) <input type="text" formControlName="list_item" placeholder="List Item" class="form-control">
    </div>
    <a (click)="addListItem()">Add List Item +</a>

  </div>
  
  <button type="submit">Submit</button>
</form>

The title works just fine, but I can't find the error I have with the "formControlName", which is causing the error.

Update with what I changed list.component.html

<h2>Add List </h2>

<form [formGroup]="listForm" novalidate (ngSubmit)="addList(listForm)">
  <div class="form-group">
    <input type="text" class="form-control" formControlName="title" placeholder="Title">
  </div>
  
  <div formArrayName="list_items">
    <div *ngFor="let list_item of listForm.controls.list_items.controls; let i=index" class="panel panel-default">
      {{i + 1}}.) <input type="text" formControlName="{{i}}" placeholder="List Item" class="form-control">
    </div>
    <a (click)="addListItem()">Add List Item +</a>

  </div>
  
  <button type="submit">Submit</button>
</form>

And in my component I changed the constructor and my addListItem method:

listForm: FormGroup;

  constructor(private nodeService: NodeService, private messageService: MessageService, private fb: FormBuilder,
                private listService: ListService) { 
    this.listForm = this.fb.group({
      title: ['', [Validators.required, Validators.minLength(5)]],
      list_items: this.fb.array([
          [''],
        ])
    });
  }

  addListItem() {
    const control = <FormArray>this.listForm.controls['list_items'];
    control.push(this.fb.control(['']));
    console.log(control)
  }
Irving answered 24/9, 2016 at 18:28 Comment(0)
I
49

There should be a formControlName in your HTML form mapped to your component file.

<div *ngFor="let list_item of [0,1,2]; let i=index" class="panel panel-default">
  {{i + 1}}.) <input type="text" formControlName="{{i}}" placeholder="List Item" class="form-control">
</div>
list_items: this.fb.array([
    [''], //0 points to this
    [''], //1 points to this
    [''] //2 points to this
])
Irritable answered 24/9, 2016 at 20:1 Comment(6)
Isn't this going to interfer with what I try to achieve? I have my ´initListItem()´ which returns the ´list_item´ and therefor makes it possible to add new forms dynamically. Do you have another suggestion on how to achieve this?Irving
Thanks so much you led me in the right direction. I will post what I changed in the main post :)!Irving
@Irving that'd be actually awesome :)Dextrad
There is an update with what I change in my post ;)!Irving
@Irritable If I want my formControlName to be more descriptive (like item-{{i}}) how would I match that in the formbuilder array? Is it possible? I tried replacing [''] with {'item-0': ''}, and a few other things but it didn't work and I can't find an example like this.Millpond
make sure the length of [0,1,2].length is equal to or less than the formArray lengthDoriandoric
M
42

Note that if your FormArray contains other FormGroup controls (which contain other instances of FormControl), you'll need to do this to access the controls inside each FormGroup:

        <div *ngFor="let item of myFormArray.controls; let i=index">
            <div formGroupName="{{i}}">
                <input formControlName="myFormGroupSubControl1" />
                <input formControlName="myFormGroupSubControl2" />
Middlemost answered 5/9, 2018 at 23:41 Comment(4)
I took one afternoon to know that ` [formGroupName]="i"` is MANDATORY using nested arrays.Piecework
Thank you good sir. This is what I was missing!! formGroupName on the container of the controls.Woodwork
This is very tricky, thanks a lot for saving me hours of debuggingDarondarooge
@AlissonGomes saved me sooo much time, thank youKassiekassity
R
13

A simple example with FormArray @ stackblitz (gist'd below)

app.component.ts

import { Component,OnInit } from '@angular/core';
import { FormGroup, FormControl, FormArray, FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
  fg: FormGroup;
  constructor(private fb: FormBuilder){}
  ngOnInit() {
  this.fg = this.fb.group({
    address: this.fb.group({
      street: ['', Validators.required],
    }),
    aliases: this.fb.array([])
  });
  const fa = (this.fg.get('aliases')as FormArray);
  this.addNewAlias();
  }
  addNewAlias(){
    const fa = (this.fg.get('aliases')as FormArray);
    fa.push(this.fb.group({
      name: ['', Validators.required]
    }));
  }
  deleteAlias(i:number){
    const fa = (this.fg.get('aliases')as FormArray);
    fa.removeAt(i);
    if(fa.length===0) this.addNewAlias();
  }
}

app.component.html

<form [formGroup]="fg" class="spaced">
  <h3>Nested in Group:</h3>
  <div formGroupName="address" class="spaced">
    <label>
      <input type="text" formControlName="street">
      valid: {{fg.get('address').get('street')?.valid}}
    </label>
  </div>
  <h3>Nested in Array:</h3>
  <div formArrayName="aliases" *ngFor="let alias of fg.get('aliases').controls; let i = index;" class="border">
    <div [formGroupName]="i">
      <label>
        Alias {{i+1}}:
        <input formControlName="name" placeholder="Item name">valid: {{alias.get('name')?.valid}}
      </label>
      <button type="button" (click)="deleteAlias(i)">X</button>
    </div>
  </div>
  <button type="button" (click)="addNewAlias()">add</button>
</form>
<div>form valid: {{fg?.valid}}</div>
Routinize answered 27/7, 2019 at 21:6 Comment(1)
formArrayName that was what i was looking for, thanksFlipper
L
6

You must use the interpolation.

Instead of:

    <div
      class="form-group"
      *ngFor="let hobbyControl of getControls(); let i = index">
      <input type="text" class="form-control" formControlName="i">
    </div>

You must use the interpolation with "formControlName":

    <div
      class="form-group"
      *ngFor="let hobbyControl of getControls(); let i = index">
      <input type="text" class="form-control" [formControlName]="i">
    </div>

If you're a using a recent version of Angular, make sure to do the operations in the TypeScript file, instead of in the Web page, as shown below in the file "app.component.ts":

  getControls() {
    return (<FormArray>this.signupForm.get('hobbies')).controls;
  }
Lydalyddite answered 9/1, 2020 at 12:9 Comment(0)
H
2

I also encountered this error, and I solved this problem by initiating the variables of class(elements in this.fb.array([])).

Code Snippet

    mobileNumbers: this.fb.array([this.fb.group(new MobileNumber('IN'))]),

Where class MobileNumber is used.

export class MobileNumber{
    public country_code: string;
    public mobile_number: string;
    constructor($cc){
        this.country_code = COUNTRY_CODES[$cc];
    }
}

To

export class MobileNumber{
    public country_code = '';
    public mobile_number = '';
    constructor($cc){
        this.country_code = COUNTRY_CODES[$cc];
    }
}
Handmaid answered 19/6, 2017 at 12:37 Comment(0)
G
2

-> Adding FormArray in component file

 //TO USE FormArray (set form1:any;)
 form1:any;

  hobbies: new FormArray([
     new FormControl()
  ])

-> To Add/Delete controls to/from hobbies

 addHobbies(){
   this.form1.get('hobbies').push(new FormControl());
 }

removeHobbies(index:number){
   this.form1.get('hobbies').removeAt(index);
}

-> Template File <!------- [FormArray] ADD HOBBIES------->

<div formArrayName="hobbies">
   <label for="hobbies">Hobbies</label>
   <div *ngFor="let hobby of form1.get('hobbies').controls;index as i">
    <input type='text' id="hobbies" class="form-control" placeholder="hobbies" [formControlName]="i">
    <button type="button" (click)="removeHobbies(i)">remove</button>
  </div>
  <button type="button (click)="addHobbies()">Add</button> 
 </div>
Goodish answered 7/2, 2021 at 16:46 Comment(0)
R
2

This is working in angular 14 [formGroupName]="i" is MANDATORY using nested arrays.

 getControls() {
    return (<FormArray>this.signupForm.get('yourCustomInputsName')).controls;
  }

you can use any name for controlname

<div
     formArrayName="yourCustomInputsName"
      *ngFor="let item of getControls(); let i = index">
       <div [formGroupName]="i">
      <input type="text"  formControlName="nameOfyourControl">
        </div>
    </div>
Ratiocinate answered 10/11, 2022 at 18:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.