skip to Main Content

Hi i have a component template recipe-edit.component.html:

<div class="row">
  <div class="col-xs-12">
    <form [formGroup]="recipeForm" (ngSubmit)="onSubmit()">
      <div class="row">
        <div class="col-xs-12">
          <button type="submit" class="btn btn-success">Save</button>
          <button type="button" class="btn btn-danger">Cancel</button>
        </div>
      </div>
      <div class="row">
        <div class="col-xs-12">
          <div class="form-group">
            <label for="name">Name</label>
            <input
              type="text"
              id="name"
              class="form-control"
              formControlName="name"
            />
          </div>
        </div>
      </div>
      <div class="row">
        <div class="col-xs-12">
          <div class="form-group">
            <label for="imagePath">Image URL</label>
            <input
              type="text"
              id="imagePath"
              class="form-control"
              formControlName="imagePath"
            />
          </div>
        </div>
      </div>
      <div class="row">
        <div class="col-xs-12">
          <div class="form-group">
            <img src="" class="img-responsive" />
          </div>
        </div>
      </div>
      <div class="row">
        <div class="col-xs-12">
          <div class="form-group">
            <label for="description">Description</label>
            <textarea
              type="text"
              id="description"
              class="form-control"
              rows="6"
              formControlName="description"
            ></textarea>
          </div>
        </div>
      </div>
      <div class="row">
        <div class="col-xs-12" formArrayName="ingredients">
          <div
            class="row"
            *ngFor="let ingredientCtrl of controls; let i = index"
            [formGroupName]="i"
          >
            <div class="col-xs-8">
              <input
                type="text"
                class="form-control"
                [formControlName]="name"
              />
            </div>
            <div class="col-xs-2">
              <input
                type="number"
                class="form-control"
                [formControlName]="amount"
              />
            </div>
            <div class="col-xs-2">
              <button class="btn btn-danger">X</button>
            </div>
          </div>
        </div>
      </div>
    </form>
  </div>
</div>

And the corresponding ts recipe-edit.component.ts is:

import { Component, OnInit } from '@angular/core';
import { FormArray, FormControl, FormGroup, NgForm } from '@angular/forms';
import { ActivatedRoute, Params } from '@angular/router';
import { RecipeService } from '../recipe.service';

@Component({
  selector: 'app-recipe-edit',
  templateUrl: './recipe-edit.component.html',
  styleUrls: ['./recipe-edit.component.css'],
})
export class RecipeEditComponent implements OnInit {
  recipeForm: FormGroup;
  id: number;

  editMode: boolean = false;

  constructor(
    private route: ActivatedRoute,
    private recipeService: RecipeService
  ) {}

  ngOnInit() {
    this.route.params.subscribe((params: Params) => {
      this.id = +params['id'];
      this.editMode = params['id'] != null;
      this.initForm();
    });
  }

  private initForm() {
    let recipeName = '';
    let recipeImagePath = '';
    let recipeDescription = '';
    let recipeIngredients = new FormArray([]);

    if (this.editMode) {
      const recipe = this.recipeService.getRecipe(this.id);
      recipeName = recipe.name;
      recipeImagePath = recipe.imagePath;
      recipeDescription = recipe.description;
      if (recipe['ingredients']) {
        for (let ingredient of recipe.ingredients) {
          recipeIngredients.push(
            new FormControl({
              name: new FormControl(ingredient.name),
              amount: new FormControl(ingredient.amount),
            })
          );
        }
      }
    }

    this.recipeForm = new FormGroup({
      name: new FormControl(recipeName),
      imagePath: new FormControl(recipeImagePath),
      description: new FormControl(recipeDescription),
      ingredients: recipeIngredients,
    });
  }

  get controls() {
    return (<FormArray>this.recipeForm.get('ingredients')).controls;
  }

  onSubmit() {
    console.log(this.recipeForm);
  }
}

But somehow in the console i get the following error when trying to reach the endpoint:

ERROR Error: Cannot find control with path: 'ingredients -> 0 -> '

multiple times… Can someone explain me what is happening here and why?

The problem is in the code that generates the array of formControls at the end of the template when using *ngFor angular directive

Thank you in advice!

I’ve tried to generate new array to extract only the value of the FormControl like this:

get controls() {
    return (<FormArray>this.recipeForm.get('ingredients')).controls.map(
      (control) => control.value
    );
  }

But it is not the solution and is bad code i don’t want this

2

Answers


  1. Chosen as BEST ANSWER

    I Found the problem...

    starting with the mapping of controls in my template:

    [formControlName]="name"
    

    must be:

    formControlName="name"
    

    It's like that because I want to assign to the property formControlName a static value "name" which is a string and not the corresponding value of a property named name.

    Then to add multiple controls to a FormControlArray you have to add a new FormGroup with nested FormControls so:

    recipeIngredients.push(
      new FormControl({
        name: new FormControl(ingredient.name),
        amount: new FormControl(ingredient.amount),
      })
    );
    

    becomes:

    recipeIngredients.push(
      new FormGroup({
        name: new FormControl(ingredient.name),
        amount: new FormControl(ingredient.amount),
      })
    );
    

    Hope that can help you out


  2. we should always have a formgroup contain the form controls for the form array, then please refer the below stackblitz on how the html is modified inorder to make the changes work.

    html

    <div class="row">
      <div class="col-xs-12">
        <form [formGroup]="recipeForm" (ngSubmit)="onSubmit()">
          <div class="row">
            <div class="col-xs-12">
              <button type="submit" class="btn btn-success">Save</button>
              <button type="button" class="btn btn-danger">Cancel</button>
            </div>
          </div>
          <div class="row">
            <div class="col-xs-12">
              <div class="form-group">
                <label for="name">Name</label>
                <input
                  type="text"
                  id="name"
                  name="name"
                  class="form-control"
                  formControlName="name"
                />
              </div>
            </div>
          </div>
          <div class="row">
            <div class="col-xs-12">
              <div class="form-group">
                <label for="imagePath">Image URL</label>
                <input
                  type="text"
                  id="imagePath"
                  name="imagePath"
                  class="form-control"
                  formControlName="imagePath"
                />
              </div>
            </div>
          </div>
          <div class="row">
            <div class="col-xs-12">
              <div class="form-group">
                <img src="" class="img-responsive" />
              </div>
            </div>
          </div>
          <div class="row">
            <div class="col-xs-12">
              <div class="form-group">
                <label for="description">Description</label>
                <textarea
                  type="text"
                  id="description"
                  name="description"
                  class="form-control"
                  rows="6"
                  formControlName="description"
                ></textarea>
              </div>
            </div>
          </div>
          <div class="row">
            <div class="col-xs-12" formArrayName="ingredients">
              <div
                class="row"
                *ngFor="
                  let ingredientCtrl of recipeForm.controls.ingredients.controls;
                  let i = index
                "
              >
                <div class="col-xs-8">
                  <input
                    type="text"
                    class="form-control"
                    [formControl]="ingredientCtrl.controls.name"
                  />
                </div>
                <div class="col-xs-2">
                  <input
                    type="number"
                    class="form-control"
                    [formControl]="ingredientCtrl.controls.amount"
                  />
                </div>
                <div class="col-xs-2">
                  <button class="btn btn-danger">X</button>
                </div>
              </div>
            </div>
          </div>
        </form>
      </div>
    </div>
    
    {{ recipeForm.value | json }}
    

    ts

    import { Component, OnInit } from '@angular/core';
    import { FormGroup, FormControl, Validators, FormArray } from '@angular/forms';
    import { ActivatedRoute, Params } from '@angular/router';
    import { Observable } from 'rxjs/Observable';
    
    @Component({
      selector: 'my-app',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css'],
    })
    export class AppComponent implements OnInit {
      recipeForm: FormGroup;
      id: number;
    
      editMode: boolean = true;
    
      constructor() {}
    
      ngOnInit() {
        this.initForm();
      }
    
      private initForm() {
        let recipeName = '';
        let recipeImagePath = '';
        let recipeDescription = '';
        let recipeIngredients = new FormArray([]);
    
        if (this.editMode) {
          const recipe = {
            name: 'test',
            imagePath: 'test',
            description: 'test',
            ingredients: [
              { name: 'test', amount: 100 },
              { name: 'test2', amount: 100 },
            ],
          };
          recipeName = recipe.name;
          recipeImagePath = recipe.imagePath;
          recipeDescription = recipe.description;
          if (recipe['ingredients']) {
            for (let ingredient of recipe.ingredients) {
              recipeIngredients.push(
                new FormGroup({
                  name: new FormControl(ingredient.name),
                  amount: new FormControl(ingredient.amount),
                })
              );
            }
          }
        }
    
        this.recipeForm = new FormGroup({
          name: new FormControl(recipeName),
          imagePath: new FormControl(recipeImagePath),
          description: new FormControl(recipeDescription),
          ingredients: recipeIngredients,
        });
      }
    
      get controls() {
        return (<FormArray>this.recipeForm.get('ingredients')).controls;
      }
    
      onSubmit() {
        console.log(this.recipeForm);
      }
    }
    
    // angular form is group of controls
    

    stackblitz

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search