Error Populating FormArray using FormGroup in a for loop: Angular 17

46 Views Asked by At

I am getting the following error when trying to populate a FormArray in Angular (version 17):

Argument of type 'FormGroup<{ name: FormControl<string | null>; amount: FormControl<number | null>; }>' is not assignable to parameter of type 'never'.ts(2345)

This issue happens in line 44 when I try to push the FormGroup (with all the FormControls) in the FormArray. Also, I've tried using FormBuilder with the fb.group() and tried also to cast the FormArray to no good results. I don't know what to do next to solve this issue. I've tried ChatGPT, the internet, and Angular documents. Also, I think the error is misleading as I think there is something hidden that I'm not seeing for this particular Angular version (17).

Here's my code:

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

@Component({
  selector: 'app-recipe-edit',
  templateUrl: './recipe-edit.component.html',
  styleUrl: './recipe-edit.component.css'
})
export class RecipeEditComponent implements OnInit{
  id!: number
  editMode = false
  recipeForm!:FormGroup

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

  ngOnInit(): void { // This is an angular Observable which is going to be managed/destroy by Angular
    // If you create a custom Observable you need to destroy it manually...!!!!
    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 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() { // a getter!
  return (<FormArray>this.recipeForm.get('ingredients')).controls;
}

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

}

Here's the ng version: Angular Version

2

There are 2 best solutions below

1
Yong Shun On

In the latest Angular version, Reactive Forms are Typed Forms.

You need to define the type for your recipeIngredients FormArray.

export interface IngredientForm {
  name: FormControl<string>;
  amount: FormControl<number>;
}
let recipeIngredients: FormArray<FormGroup<IngredientForm>> = new FormArray(
  [] as FormGroup<IngredientForm>[]
);
get controls() { // a getter!
  return (<FormArray<FormGroup<IngredientForm>>>this.recipeForm.get('ingredients')).controls;
}
0
Jose Luis Roman On

This is the code after the fixes suggested by Yong Shun:

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

export interface IngredientForm {
  'name': FormControl<string | null>;
  'amount': FormControl<number | null>;
}

@Component({
  selector: 'app-recipe-edit',
  templateUrl: './recipe-edit.component.html',
  styleUrl: './recipe-edit.component.css'
})
export class RecipeEditComponent implements OnInit{
  id!: number
  editMode = false
  recipeForm!:FormGroup

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

  ngOnInit(): void { // This is an angular Observable which is going to be managed/destroy by Angular
    // If you create a custom Observable you need to destroy it manually...!!!!
    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: FormArray<FormGroup<IngredientForm>> = new FormArray(
      [] as FormGroup<IngredientForm>[]
    );

    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']){
          const newFormGroup = new FormGroup({
            'name':  new FormControl(ingredient.name, Validators.required),
            'amount': new FormControl(ingredient.amount, Validators.required)
          })
          recipeIngredients.push(newFormGroup)
        }
      }
    }
    this.recipeForm = new FormGroup({
      'name': new FormControl(recipeName),
      'imagePath': new FormControl(recipeImagePath),
      'description': new FormControl(recipeDescription),
      'ingredients': recipeIngredients
    })
  }

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

  get controls() { // a getter!
    return (<FormArray<FormGroup<IngredientForm>>>this.recipeForm.get('ingredients')).controls;
  }

}