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