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

Extract a property from a JSON object

Is there a way to access the href properties and use them to create multiple img elements with their sources set as the extracted href properties? I'm looking for a solution in either javascript or jQuery. I attempted the following code, but it didn& ...

Issue with Web Worker functionality in SvelteKit on Firefox version 106.0.5

I've set up a function in my main file like this: const loadWorker = async () => { const SyncWorker = await import("$lib/canvas.worker?worker"); syncWorker = new SyncWorker.default(); syncWorker?.postMessage({}); ...

using database URL as an AJAX parameter

I am currently working on a Python CherryPy controller that needs to validate a database URL by attempting a connection. However, I am facing challenges with passing the parameter to the method. Below is my AJAX call: $.ajax({ async: false, ty ...

Unable to retrieve basic profile data from LinkedIn Members using their email ID unless they are signed in

I am struggling to retrieve the basic profile details of Linkedin Members using their email ID. Despite my efforts, I haven't been able to find relevant information in the documentation. My attempt involved creating an app, initializing the JavaScrip ...

Managing JSON responses from a server using Javascript

I have encountered various similar issues, but none of them have provided a solution for my specific question. On my server, I generate a JSON string and place it in the response: List<String> list = getSomeList(); JSONArray jsArray = new JSONArray( ...

Script tag in NextJS

After numerous attempts, I am still struggling with a specific task on this website. The challenge is to insert a script tag that will embed a contact form and newsletter sign-up form, among others, on specific pages of the site. For instance, the contact ...

Accessing the value of an object nested within another object in Angular

I have encountered numerous similar topics, but after going through all of them, I still cannot pinpoint what I am doing incorrectly. The data I retrieve is from DEXIE (indexedDB) where my record is stored in the following format: async addRequestToLocalD ...

Running a function before triggering a refresh in Angular 2/4

When a user clicks or presses the F5 button on an HTML page, the page refreshes. However, before the refresh occurs, I want to execute a function or display a simple alert. The user can trigger a refresh by clicking the refresh button, pressing F5, or usi ...

Is there a way to identify when a user is returning to a previous page in Angular2

How can I detect if a user has pressed the back button in their browser to navigate back while using Angular? Currently, I am subscribing to router events to achieve this. constructor(private router: Router, private activatedRoute: ActivatedRoute) { ...

The specified module '...' is identified as a non-module entity and therefore cannot be imported using this specific construct

Currently, I am facing an issue in my .tsx file where I am attempting to import a RaisedButton component from material-ui using the following code: import * as RaisedButton from 'material-ui/lib/raised-button' Unfortunately, this is triggering ...

HTML5 input placeholder adapts its size and position dynamically as data is being

During my interaction with the input fields on my bank's website, I noticed that the placeholder text undergoes a unique transformation. It shrinks in size and moves to the upper-left corner of the input field while I am entering information. Unlike t ...

The controller failed to return a value when utilizing the factory

I am attempting to pass a value from my view to the controller using a function within the ng-click directive. I want to then use this value to send it to my factory, which will retrieve data from a REST API link. However, the value I am sending is not ret ...

Dynamically importing files in Vue.js is an efficient way to

Here's the code snippet that is functioning correctly for me var Index = require('./theme/dir1/index.vue'); However, I would like to utilize it in this way instead, var path = './theme/'+variable+'/index.vue'; var Inde ...

The error message "TypeError: Trying to access properties of an undefined object (reading '800')" is being displayed

Every time I launch my application, I encounter the error message: "TypeError: Cannot read properties of undefined (reading '800')". import React, { useState } from 'react'; import { Menu, MenuItem, Avatar, Box, ThemeProvider} ...

Error: Cookie cannot be set due to headers already being sent

Here lies my code snippet import { Request, Response } from "express"; import { database } from "firebase-admin"; async function updateAccessToken( req: Request, res: Response, db: database.Database ) { try { await db ...

Having trouble with the Tap to copy discount code function not working in the Shopify cart drawer?

Our goal is to implement tap to copy functionality for code snippets on our Shopify website. It works seamlessly on the product detail page, but in the cart drawer, it only functions properly after the second page load. {% if cart.total_price > 0 % ...

Collaboratively accessing a shared constant in two separate JavaScript files

I am diving into the world of JavaScript and Node.js. I am currently experimenting with Puppeteer to extract the text value of a tag and store it in a constant variable. However, I am encountering difficulties when trying to integrate this value into my ...

Encountering a glitch while iterating through function results in failure after the initial modification

I am facing some errors with the script provided below: var sections = ["#general_info", "#address_records", "#employment_history", "#driver_experience", "#military_experience", "#eeo_survey", &qu ...

How can I use a single route in Angular 5 to direct all paths for an outlet to a single component?

Here is my current setup: const routes: Routes = [ { path: '', component: NavComponent, outlet: 'nav' }, // (1) { path: '**', component: NavComponent, outlet: 'nav' } // (2) ]; The configuration is functioning ...

A simple way to initiate an AJAX call once the page has completed loading is by using the `window.location.href` as the

I encountered an issue with my event listener setup function navBtn() { $('#navBtn').on('click', (event) => { window.location.href = 'someotherfile.html'; myAJAXrequest(); }) } The problem I'm facing is that ...