Home > Software design >  Angular Error: Cannot read properties of null (reading 'controls')
Angular Error: Cannot read properties of null (reading 'controls')

Time:12-16

I'm very new to Angular and I am currently trying to understand how FormArrays, FormGroups and FormControls work. I want to insert data to my firebase collection in the format below.

Firebase Format

My code compiles successfully but I get an error in the console that says core.js:6486 ERROR TypeError: Cannot read properties of null (reading 'controls') at AddRecipeComponent_Template (add-recipe.component.html:56). Is there something I am missing like initializations?

my model looks like this: Recipe.ts

export interface Recipe {
  id: string;
  metaData: {
    name: string;
    img: string;
    description: string;
    viewed: number;
  };
  recipeDetails: {
    ingredients: Ingredients[],
    instructions: string,
    cookingTime: string,
    servingPortion: string,
    dietaryInformation: string
  }
}

export interface Ingredients {
  name: string,
  amount: number,
  unit: string
}

export type RecommendedRecipe = { id: string } & Recipe['metaData'];
export type RecipeDetail = { id: string } & Recipe['metaData'] & Recipe['recipeDetails'];

add-recipe.component.html

<div >
  <div >

    <form [formGroup]="addRecipeForm" >

      <div formGroupName="metaData">

        <div >
          <label for="name"> Recipe Name </label>
          <input type="text" name="name"  formControlName="name" />
        </div>
        <div >
          <label for="img"> Img </label>
          <input type="text" name="img"  formControlName="img" />
        </div>
        <div >
          <label for="description"> Description </label>
          <input type="text" name="description"  formControlName="description" />
        </div>
        <div >
          <input type="number" name="viewed"  formControlName="viewed" value=1 hidden />
        </div>

      </div>

      <div formGroupName="recipeDetails">


        <div >
          <label for="cookingTime"> Cooking Time </label>
          <input type="text" name="cookingTime"  formControlName="cookingTime" />
        </div>
        <div >
          <label for="servingPortion"> Serving Portion </label>
          <input type="text" name="servingPortion"  formControlName="servingPortion" />
        </div>
        <div >
          <label for="dietaryInformation"> Dietary Information </label>
          <input type="text" name="dietaryInformation"  formControlName="dietaryInformation" />
        </div>
        <div >
          <label for="instructions"> Instructions </label>
          <textarea  name="instructions" rows="20" style="resize: none;"
            formControlName="instructions"></textarea>
        </div>

        <br/>

        <div >
          <button  (click)="addIngredients()"> Add Ingredients </button>
        </div>

        <br/>

        <!--Ingredients-->
        <div formArrayName="ingredients" *ngFor="let ing of ingredients.controls; let i = index">

          <div [formGroupName]="i">
            <div >
              <label for="name"> Ingredients Name </label>
              <input type="text" name="name"  formControlName="name" />
            </div>
            <div >
              <label for="amount"> Amount </label>
              <input type="number" name="amount"  formControlName="amount" />
            </div>
            <div >
              <label for="unit"> Unit </label>
              <select name="unit"  formControlName="unit">
                <option value=""> Please Select Unit </option>
                <option *ngFor="let unitOpt of unitOptions" [value]="unitOpt">{{ unitOpt }}</option>
              </select>
            </div>
            <div ></div>
            <div ></div>
            <br />
          </div>

        </div>
      </div>


      <div ></div>
      <div ></div>

      <div >
        <button  (click)="addRecipe()"> Add Recipe </button>
        <a href="/recipe-list" > Cancel </a>
      </div>
    </form>
  </div>
</div>

add-recipe.component.ts

import { Component, OnInit } from '@angular/core';
import { FormArray, FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { Recipe } from 'src/app/models/Recipe';
import { LoadingService } from 'src/app/services/loading.service';
import { RecipeService } from 'src/app/services/recipe.service';

@Component({
  selector: 'app-add-recipe',
  templateUrl: './add-recipe.component.html',
  styleUrls: ['./add-recipe.component.scss']
})
export class AddRecipeComponent implements OnInit {
  addRecipeForm: FormGroup;
  unitOptions: string[] = [
    'Piece(s)',
    'Slice(s)',
    'Liter(s)',
    'Milliliter(s)',
    'Gram(s)',
    'Kilogram(s)'
  ]

  constructor(
    private recipeService: RecipeService,
    private loadingService: LoadingService,
    private router: Router,
    private fb: FormBuilder
  ) {
    this.addRecipeForm = this.fb.group({
      metaData: this.fb.group({
        name: [''],
        img: [''],
        description: [''],
        viewed: ['']
      }),
      recipeDetails: this.fb.group({
        instructions: [''],
        cookingTime: [''],
        servingPortion: [''],
        dietaryInformation: [''],
        ingredients: this.fb.array([
          this.addIngredientsFormGroup()
        ],Validators.required)
      })   
    });
  }

  ngOnInit() {
  }

  public addIngredientsFormGroup(): FormGroup {
    return this.fb.group({
      name: [''],
      amount: [''],
      unit: ['']
    })
  }

  get ingredients():FormArray{
    return <FormArray> this.addRecipeForm.get('ingredients');
  }

  addIngredients() {
    this.ingredients.push(this.addIngredientsFormGroup());
  }

  public addRecipe(): void {
    // bind to Recipe Model
    var newRecipe = {
      metaData: this.addRecipeForm.value.metaData,
      recipeDetails: this.addRecipeForm.value.recipeDetails
    } as unknown as Recipe;

    console.log('addRecipeForm -> ', newRecipe);

    this.recipeService.createRecipe(newRecipe)
    .subscribe(
      (result) => {
        console.log("add result", result);
        this.router.navigateByUrl('/recipe-list');
      }
    );
  }

  onSubmit(): void {
    console.log(this.addRecipeForm);
  }

}

CodePudding user response:

Your FormArray path in your getter is wrong, your formarray is inside recipeDetails formgroup, so that is where you need to point to:

get ingredients(): FormArray {
  return <FormArray>this.addRecipeForm.get('recipeDetails.ingredients');
}
  • Related