Angular - Get errors in reactive form with loop for the FormArray

90 Views Asked by At

I'm using Angular CLI version 16.2.1.

I'm following a course and I'm using reactive forms: In a 'Recipe' list I need to add a Recipe with some inputs and a list of ingredients. The problem is that everytime I try to add an ingredient (with a *ngFor of controls), I get errors.

HTML

<div class="row">
    <div class="col-xs-12" formArrayName="ingredients">
        <div class="row" *ngFor="let ingredientCtrl of getControls(); let i = index" [formGroupName]="i" style="margin-top: 10px;">
            <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>
        <hr>
        <div class="row">
            <div class="col-xs-12">
                <button type="button" class="btn btn-success" (click)="onAddIngredient()">Add Ingredient</button>
            </div>
        </div>
    </div>
</div>

TypeScript (with just the code, no imports)

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 (const ingredient of recipe['ingredients']) {
                recipeIngredients.push(new FormGroup({
                    'name': new FormControl(ingredient.name, Validators.required),
                    'amount': new FormControl(ingredient.amount, [Validators.required, Validators.pattern(/^[1-9]+[0-9]*$/)])
                }));
            }
        }
    }

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

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

onAddIngredient() {
    (<FormArray>this.recipeForm.get('ingredients'))?.push({
        'name': new FormControl(null, Validators.required),
        'amount': new FormControl(null, [Validators.required, Validators.pattern(/^[1-9]+[0-9]*$/)])
    })
}

View and errors

enter image description here

Any idea on how to fix this?

2

There are 2 best solutions below

1
Yong Shun On BEST ANSWER

In onAddIngredient method, you should add the FormGroup instance into FormArray but your existing code is adding an object into FormArray.

onAddIngredient() {
  let fg: FormGroup<any> = new FormGroup({
    name: new FormControl(null, Validators.required),
    amount: new FormControl(null, [
      Validators.required,
      Validators.pattern(/^[1-9]+[0-9]*$/),
    ]),
  });

  (<FormArray>this.recipeForm.get('ingredients'))?.push(fg);
}

Demo @ StackBlitz

0
Eliseo On

NOT use a function getControls(), better use a getter

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

and iterate over

*ngFor="let ingredientCtrl of ingredientsFormArray.controls";

Futhermore, I suggest you create two functions:

  1. Function that return a formGroup of ingredients

    ingredientFormGroup(data:any=null)
    {
       data=data || {name:'',amount:''}
       return new FormGroup({
        name: new FormControl(data.name, Validators.required),
        amount: new FormControl(data.amount, [
          Validators.required,
          Validators.pattern(/^[1-9]+[0-9]*$/),
        ]),
      });
    }
    
  2. And a function that return your formGroup

    formGroup(data:any=null) {
      data=data || {name:'',imagePath:'',descriptions:'',ingredients:[]}
      return new FormGroup({
            'name': new FormControl(data.name, Validators.required),
            'imagePath': new FormControl(data.imagePath, Validators.required),
            'description': new FormControl(data.description, Validators.required),
            'ingredients': new FormArray(data.ingredients
                   .map((x:any)=>this.ingredientFormGroup(x))
                   )
        })
    }
    

This allow you write

if(this.editMode) {
    const recipe = this.recipeService.getRecipe(this.id);
    this.recipeForm=this.formGroup(recipe)
}
else
    this.recipeForm=this.formGroup()

And

//to add a new incredient
this.ingredientsFormArray.push(this.ingredientFormGroup())

NOTE: I imagine that the before not work because your this.recipeService.getRecipe() should return an observable, not an object (rememeber: "services return observables, we subscribe in components" and the correct is

this.recipeService.getRecipe(this.id).subscribe((recipe:any)=>{
    this.recipeForm=this.formGroup(recipe)
 });