Creating formGroups dynamically for each object in an array and then updating the values with the object data

What I am aiming to accomplish: My goal is to dynamically generate a new formGroup for each recipe received from the backend (stored in this.selectedRecipe.ingredients) and then update the value of each formControl within the newly created formGroup with the respective data received.

What I have attempted so far: Up to this point, here is what I have tried. Currently, it is partially functional, however, the issue lies in only one instance of formGroup being created that only updates the value of the last recipe from the this.selectedRecipe.ingredients array instead of iterating through each recipe. Ultimately, my objective is to establish a unique formGroup instance for every recipe in the this.selectedRecipe.ingredients array and update the value of each formControl accordingly using the data provided.

If you have any suggestions on how I can further proceed or any insights into what might be causing the issue, I would greatly appreciate your input. Thank you!

HTML

<form [formGroup]="editRecipeF" (ngSubmit)="onSubmitEditRecipeForm()">
    <!-- Form fields here -->
  </form>

TypeScript

@Component({
  // Component details here
})
export class EditRecipeComponent implements OnInit {
  editRecipeF!: FormGroup;
  recipeId: any;
  selectedRecipe!: Recipe;

  constructor(
    // Constructor details here
  ) {}

  ngOnInit() {
    // Initialization logic here
  }

  private createIngFormGroup() {
    return new FormGroup({
      // Form control creation here
    });
  }

  public getControls() {
    return (<FormArray>this.editRecipeF.get("ingredients")).controls;
  }

  public onAddIngredients() {
    // Logic for adding ingredients here
  }

  public onSubmitEditRecipeForm() {
    if (this.editRecipeF.valid) {
      // Submission logic here
    }
  }
}

Answer №1

To ensure every element in the FormArray has a path value assigned

    const ing = this.editRecipeF.get("ingredients") as FormArray;
    ing.clear(); //<--remove all elements of the formArray;
    for (let ingredient of this.selectedRecipe.ingredients) {
      let i=ing.length;
      ing.insert(this.createIngFormGroup()) //<--add an empty formGroup
      ing.at(i).patchValue([  //<--see the at(i)
        {
          name: ingredient.ingName,
          qty: ingredient.ingQty,
          qtyUnit: ingredient.ingQtyUnit,
          imageUrl: ingredient.ingImageUrl,
        },
      ]);
    }

The approach here is to create a FormArray with as many elements as ingredients exist

    const ing = this.editRecipeF.get("ingredients") as FormArray;
    ing.clear(); //<--remove all elements of the formArray;

    //Create matching number of elements as ingredients
    for (let ingredient of this.selectedRecipe.ingredients) {
      let i=ing.length;
      ing.insert(this.createIngFormGroup()) //<--add an empty formGroup

    //Assign the values using patchValue()
    ing.at(i).patchValue(  //<--see the at(i) and remove the "["
        {
          name: ingredient.ingName,
          qty: ingredient.ingQty,
          qtyUnit: ingredient.ingQtyUnit,
          imageUrl: ingredient.ingImageUrl,
        },
      );
    }

An improvement can be achieved by utilizing patchValue effectively within the entire FormGroup structure to set values at one go

this.editRecipeF.patchValue({
    recipeDetails: {
            title: ..,
            imageUrl: ...,
            ...
            },
    ingredients:[
            {
              name:...
              qty: ...
              ...
            },    
            {
              name:...
              qty: ...
              ...
            },    
            {
              name:...
              qty: ...
              ...
            },    
    ]
  })

While updating data, it's essential to check and maintain the correct number of elements in the FormArray to accurately handle pathValue assignment

A suggestion would be to tweak createIngFormGroup() method to accept object data for dynamic form creation. For instance:

private createIngFormGroup(data:any=null) {
    data=data || {ingName:null,ingQty:0,ingQtyUnit:null,ingImageUrl:null}

    return new FormGroup({
      name: new FormControl(data.ingName, Validators.required),
      qty: new FormControl(data.ingQty, Validators.required),
      qtyUnit: new FormControl(data.ingQtyUnit, Validators.required),
      imageUrl: new FormControl(data.ingImageUrl, Validators.required),
    });
  }

By using this approach, you can now call createIngFormGroup() without or with custom data parameters

  this.createIngFormGroup() //Returns FormGroup with default values

  this.createIngFormGroup({ //Returns FormGroup with specified values
     ingName:"sugar",
     ingQty:20,
     ingQtyUnit:'spoon',
     ingImageUrl:'/images/sugar.jpg'

  })

In addition, a unified approach is demonstrated for creating a formGroup named "editRecipeF" using createForm() function

private createForm(data:any=null){
  data=data || {recipeDetails: {title:null,imageUrl:null,duration:0,calories:0},
                ingredients:[null]}

  return new FormGroup({
     recipeDetails:new FormGroup({
       title:new FormControl(data.recipeDetails.title,Validators.required),
       imageUrl:new FormControl(data.recipeDetails.imageUrl,Validators.required),
       duration:new FormControl(data.recipeDetails.duration,Validators.required),
       calories:new FormControl(data.recipeDetails.calories,Validators.required)
     }),
     ingredients:new FormArray(data.ingredients.map(x=>this.createIngFormGroup())
  })
}

Two important aspects to note are:

- Default value setup for "ingredients" array leveraging an array containing a single null element

- Utilization of map function on the array of ingredients to convert it into an array of corresponding formGroups

Initiate the form setup either in ngOnInit:

ngOnInit()
{
    this.editRecipeF=this.createForm()
}

Or within a subscription block:

 this.recipesService.fetchRecipeDetails(this.recipeId).subscribe(
      (res:any) => {
        this.editRecipeF=this.createForm(res)

 })

Remember to align the variable names in your FormArray with those expected in the incoming data for seamless integration

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a method in Angular to restrict change detection to only the current component and its descendant components?

Is there a way to trigger an event in the child component without traversing the entire component tree from the parent? import { Component } from '@angular/core' @Component({ selector: 'my-app', template: '<b>{{ te ...

Solving Mixed Content Issues in JavaScript

I'm attempting to retrieve information from OMDB API, but I'm encountering an issue with mixed content images. OMDB pulls its data from IMDB, which does not allow the use of https images. Therefore, all image sources must be prefixed with http. ...

Ways to effectively test a custom hook event using Enzyme and Jest: A guide on testing the useKeyPress hook

Looking for guidance on testing a custom hook event called useKeyPress with Enzyme and Jest This is my current custom hook for capturing keyboard events and updating keyPress value: import React, { useEffect, useState } from 'react' const useKe ...

Tips for having <script> update onchange instead of just onload

Is there a way to update the output of the <table id="mortgagetable"> each time a user changes the input values in the form? Currently, it only updates on load. Additionally, the content of the <div id="years" style="display:inline-block;">25 ...

Creating a custom JavaScript library using an existing npm module (codius)

Embarking on a new journey with this, never tried it before. Currently utilizing https://github.com/codius/codius-host. The development of Codiu§ has been abandoned, however I am determined to salvage some parts of it for my own project. It is crucial fo ...

Building a static website with the help of Express and making use of a public directory

It seems that I am facing a misunderstanding on how to solve this issue, and despite my efforts in finding an answer, the problem persists. In one of my static sites, the file structure is as follows: --node_modules --index.html --server.js --app.js The ...

Tips for Deactivating One Button While Allowing Others to Remain Active

Whenever the $order_status is marked as Accepted, only the button labeled Accept should be disabled. If the $order_status changes to Dispatched, then the button that should be disabled is labeled as Sent. However, if the status is Cancelled, then the butto ...

Exploring modifications in axis for Google charts timeline

Can anyone help me figure out how to set my Google Timeline chart to always display 24 hours on the x-axis? Currently, it automatically changes based on the earliest and latest points, but I want it to consistently show all 24 hours. For example: ...

How can I automatically close the menu when I click on a link in Vue?

Tap the menu icon Select a link The URL changes in the background. However, the menu remains open. How do I close the menu when a link is selected? The menu is wrapped in a details HTML element. Is there a way to remove the "open" attribute from the detai ...

I'm baffled by the constant undefined status of the factory in AngularJS

I have encountered an issue where I defined a factory that makes a get request, but when I inject it into a controller, it always throws an undefined error. Below is the code for the factory: (function() { 'use strict'; var app = angul ...

Show only child elements of a specific type within the parent div

Looking to identify divs with the class 'test' that contain only buttons in their child nodes. This is the HTML code that needs to be filtered. <div class="test"> <div> <button> <span>Button 1</span></butto ...

The JQuery ajax post function is typically called towards the conclusion of a JavaScript script

I am struggling with validating whether a username is already taken. I have been attempting to determine if the username exists by utilizing the "post" method in jQuery. However, every time I execute this function, the script seems to skip to the end of th ...

Tips for implementing a search filter in a select dropdown menu using Angular

I am attempting to enhance my select option list by including a search filter. With numerous options available, I believe that having a search function will make it easier for the user to locate their desired selection. I hope my message is clear despite ...

Steps for generating an instance of a concrete class using a static method within an abstract class

Trying to instantiate a concrete class from a static method of an abstract class is resulting in the following error: Uncaught TypeError: Object prototype may only be an Object or null: undefined This error occurs on this line in ConcreteClass.js: re ...

Dealing with 404 page not found error without replacing the default in Next.js 13

I followed the Next.js 13 documentation's suggestion to create a file called not-found.jsx in my app directory to handle 404 errors. But, despite placing it inside the app directory and intended for layout and loading purposes, it is not overriding th ...

Exploring the capabilities of Google Drive API using Requests library

I am interested in streaming a file from a remote source to Google Drive. By utilizing the request library, you can easily download files locally like so: request('http://google.com/doodle.png').pipe(fs.createWriteStream('doodle.png') ...

Location of Ajax callback function

I encountered an issue with a callback function that was placed within $(document).ready. The callback function wasn't functioning as expected. Surprisingly, when I moved it outside of $(document).ready, the code started working flawlessly. I am puzzl ...

Is it feasible to incorporate a multi-level navigation menu into the "NavItem" component using MaterialUI with TypeScript?

Instructions for creating a multi-level navigation menu using MaterialUI and TypeScript: To the existing '/questions' section, it is desired to include the following 2 navigation menus: /questions/Tags /questions/Users This should resemble the ...

Utilizing jQuery to extract the `h1` element from a dynamically loaded external page within the

I am facing a challenge in selecting an h1 element from a remote page that I have loaded into $(data). Despite several attempts, I am struggling to write the correct code. When I use this code: console.log($(data)); The output is as follows: [text, meta ...

Guide to defining the encoding of an XML file with JavaScript

Hi there, I am currently facing an issue with encoding while creating a document using JavaScript. The problem is that the document rejects all non-ascii characters. For example, when passing the string "verificación", it gets replaced by "". Any suggesti ...