如何为数组中的每个对象动态创建 formGroups,然后使用对象数据 patchValue
How to dynamically create formGroups, for each object in an array and then patchValue with the object data
我想要实现的目标: 为我从后端收到的每个食谱动态创建一个新的 formGroup(存储在 this.selectedRecipe.ingredients
),然后修补新创建的 formGroup 中每个 formControl 的值以及我收到的数据。
我尝试过的方法: 到目前为止,请看下面我到目前为止尝试过的方法,目前它可以部分工作,但问题是它正在创建只有一个 formGroup 实例,并且只修补 this.selectedRecipe.ingredients
数组中最后一个配方的值,而不是数组中每个配方的值。最后,我想要实现的是为 this.selectedRecipe.ingredients
数组中的每个配方创建一个 formGroup 实例,并使用我的数组中的信息修补 formGroup 中每个 formControl 的值。
如果你们知道我可以如何进一步解决这个问题或者我做错了什么,我将不胜感激。非常感谢!
HTML
<form [formGroup]="editRecipeF" (ngSubmit)="onSubmitEditRecipeForm()">
<div formGroupName="recipeDetails">
<div class="form-group">
<label for="title">Title</label>
<input
type="text"
id="title"
formControlName="title"
class="form-control"
/>
</div>
<div class="form-group">
<label for="imageUrl">Recipe Image</label>
<input
type="text"
id="imageUrl"
formControlName="imageUrl"
class="form-control"
/>
</div>
</div>
<div formArrayName="ingredients">
<div class="ingredients-div mt-4">
<h2>Ingredients</h2>
<button
class="float-left btn"
mat-icon-button
color="primary"
aria-label="Add"
(click)="onAddIngredients()"
matTooltip="Add"
>
<p>+</p>
</button>
</div>
<div
class="row"
*ngFor="let ingredient of getControls(); let i = index"
[formGroupName]="i"
>
<div class="form-group">
<label for="name">Ingredient Name</label>
<input
class="form-control"
type="text"
name="name"
id="name"
formControlName="name"
/>
</div>
<div class="form-group">
<label for="qty">Ingredient Qty</label>
<input
class="form-control"
type="text"
name="qty"
id="qty"
formControlName="qty"
/>
</div>
</div>
</div>
<button class="btn btn-primary mt-4" type="submit">Submit</button>
</form>
TypeScript
@Component({
selector: "app-edit-recipe",
templateUrl: "./edit-recipe.component.html",
styleUrls: ["./edit-recipe.component.css"],
})
export class EditRecipeComponent implements OnInit {
editRecipeF!: FormGroup;
recipeId: any;
selectedRecipe!: Recipe;
constructor(
private formBuilder: FormBuilder,
private recipesService: RecipesService,
private route: ActivatedRoute
) {}
ngOnInit() {
this.recipeId = this.route.snapshot.params["id"];
this.editRecipeF = this.formBuilder.group({
recipeDetails: this.formBuilder.group({
title: ["", Validators.required],
imageUrl: ["", Validators.required],
duration: ["", Validators.required],
calories: ["", Validators.required],
}),
ingredients: this.formBuilder.array([this.createIngFormGroup()]),
});
this.recipesService.fetchRecipeDetails(this.recipeId).subscribe(
(selectedRecipeDetails) => {
this.selectedRecipe = selectedRecipeDetails;
this.editRecipeF.patchValue({
recipeDetails: {
title: this.selectedRecipe.title,
imageUrl: this.selectedRecipe.imageUrl,
duration: this.selectedRecipe.duration,
calories: this.selectedRecipe.calories,
},
});
const ing = this.editRecipeF.get("ingredients") as FormArray;
for (let ingredient of this.selectedRecipe.ingredients) {
ing.patchValue([
{
name: ingredient.ingName,
qty: ingredient.ingQty,
qtyUnit: ingredient.ingQtyUnit,
imageUrl: ingredient.ingImageUrl,
},
]);
}
},
(error) => {
console.log(error);
}
);
}
private createIngFormGroup() {
return new FormGroup({
name: new FormControl("", Validators.required),
qty: new FormControl("", Validators.required),
qtyUnit: new FormControl("", Validators.required),
imageUrl: new FormControl("", Validators.required),
});
}
public getControls() {
return (<FormArray>this.editRecipeF.get("ingredients")).controls;
}
public onAddIngredients() {
const ingredients = this.editRecipeF.get("ingredients") as FormArray;
ingredients.push(this.createIngFormGroup());
}
public onSubmitEditRecipeForm() {
if (this.editRecipeF.valid) {
console.log(this.editRecipeF.value);
this.recipesService
.editRecipe(this.editRecipeF.value, this.recipeId)
.subscribe(
(success) => {},
(error) => {
console.log(error);
}
);
}
}
}
您需要 FormArray 的每个元素的路径值
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,
},
]);
}
您创建了一个 FormArray,其中包含许多元素作为成分
const ing = this.editRecipeF.get("ingredients") as FormArray;
ing.clear(); //<--remove all elements of the formArray;
//create so many elements as increadients
for (let ingredient of this.selectedRecipe.ingredients) {
let i=ing.length;
ing.insert(this.createIngFormGroup()) //<--add an empty formGroup
//make the pathValue
ing.at(i).patchValue( //<--see the at(i) and remove the "["
{
name: ingredient.ingName,
qty: ingredient.ingQty,
qtyUnit: ingredient.ingQtyUnit,
imageUrl: ingredient.ingImageUrl,
},
);
}
但我们甚至可以让事情变得更好。 PathValue 使我们的 FormGroup 具有我们指示的值。这样我们就可以完美的做出一个唯一的pathValue。有些喜欢
this.editRecipeF.patchValue({
recipeDetails: {
title: ..,
imageUrl: ...,
...
},
ingredients:[
{
name:...
qty: ...
...
},
{
name:...
qty: ...
...
},
{
name:...
qty: ...
...
},
]
})
确实,当我们收到数据时,我们可以检查我们的 FormArray 中有多少元素,add/remove 是必要的,并使 pathValue
但是当我们有一个 FormArray 时的问题是它是必要的我们的 formArray 有比我们拥有的“成分”更多的元素(如果我们的数组更少,路径值不要't 添加元素)
我建议另一种方法。想象一下,您稍微更改了函数 createIngFormGroup()
以允许接收到的对象使用数据创建 formGroup。经典是
private createIngFormGroup(data:any=null) {
//if not pass data as argument create a data "on fly"
//with values by defect
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),
});
}
所以
this.createIngFormGroup() //you get a FormGroup with empty values
this.createIngFormGroup({ //you get a FormGroup with the values indicated
ingName:"sugar",
ingQty:20,
ingQtyUnit:'spoon',
ingImageUrl:'/images/sugar.jpg'
})
此外,我们可以创建一个函数,return 一个 formGroup 类型“editRecipeF”,其想法与使用我们的函数的想法相同。我再次使用 FromGroup,而不是 FormBuilder 来为我们的代码提供“连贯性”。如果我们使用 FormBuilder,我们应该在组件的所有功能中使用 formBuilder,否则不在 where
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())
})
}
看到两件重要的事情。
首先我们的“成分”的缺陷值,我们选择将是一个具有空值的元素的数组
FormArray的创建方式。我们的formArray是FormGroup的一个FormArray,所以我们把数组data.ingredients转换成一个formGroup的数组。这是“地图”品牌:
data.map(x=>this.createIngFormGroup() //--is an array of FormGroup
我们附在new FormArray(..our array of FormGroups..)
嗯,一切都为我们准备好了。我们可以在 ngOnInit
ngOnInit()
{
this.editRecipeF=this.createForm()
}
或者我们可以,在订阅中制作
this.recipesService.fetchRecipeDetails(this.recipeId).subscribe(
(res:any) => {
this.editRecipeF=this.createForm(res)
})
就这样了。
重要说明。当您收到成分数据时,变量是ìngName、ingQty、ingQtyUnit 和 ingImageUrl,但是 formArray 的元素是 name、qty、qtyUnit 和 imageUrl(没有“ing”)。我觉得你的表单数组元素的变量有前缀“ing”更好(所以更像是你的数据库的数据更好,否则你需要 transform with/without 前缀来调用你的API)
我想要实现的目标: 为我从后端收到的每个食谱动态创建一个新的 formGroup(存储在 this.selectedRecipe.ingredients
),然后修补新创建的 formGroup 中每个 formControl 的值以及我收到的数据。
我尝试过的方法: 到目前为止,请看下面我到目前为止尝试过的方法,目前它可以部分工作,但问题是它正在创建只有一个 formGroup 实例,并且只修补 this.selectedRecipe.ingredients
数组中最后一个配方的值,而不是数组中每个配方的值。最后,我想要实现的是为 this.selectedRecipe.ingredients
数组中的每个配方创建一个 formGroup 实例,并使用我的数组中的信息修补 formGroup 中每个 formControl 的值。
如果你们知道我可以如何进一步解决这个问题或者我做错了什么,我将不胜感激。非常感谢!
HTML
<form [formGroup]="editRecipeF" (ngSubmit)="onSubmitEditRecipeForm()">
<div formGroupName="recipeDetails">
<div class="form-group">
<label for="title">Title</label>
<input
type="text"
id="title"
formControlName="title"
class="form-control"
/>
</div>
<div class="form-group">
<label for="imageUrl">Recipe Image</label>
<input
type="text"
id="imageUrl"
formControlName="imageUrl"
class="form-control"
/>
</div>
</div>
<div formArrayName="ingredients">
<div class="ingredients-div mt-4">
<h2>Ingredients</h2>
<button
class="float-left btn"
mat-icon-button
color="primary"
aria-label="Add"
(click)="onAddIngredients()"
matTooltip="Add"
>
<p>+</p>
</button>
</div>
<div
class="row"
*ngFor="let ingredient of getControls(); let i = index"
[formGroupName]="i"
>
<div class="form-group">
<label for="name">Ingredient Name</label>
<input
class="form-control"
type="text"
name="name"
id="name"
formControlName="name"
/>
</div>
<div class="form-group">
<label for="qty">Ingredient Qty</label>
<input
class="form-control"
type="text"
name="qty"
id="qty"
formControlName="qty"
/>
</div>
</div>
</div>
<button class="btn btn-primary mt-4" type="submit">Submit</button>
</form>
TypeScript
@Component({
selector: "app-edit-recipe",
templateUrl: "./edit-recipe.component.html",
styleUrls: ["./edit-recipe.component.css"],
})
export class EditRecipeComponent implements OnInit {
editRecipeF!: FormGroup;
recipeId: any;
selectedRecipe!: Recipe;
constructor(
private formBuilder: FormBuilder,
private recipesService: RecipesService,
private route: ActivatedRoute
) {}
ngOnInit() {
this.recipeId = this.route.snapshot.params["id"];
this.editRecipeF = this.formBuilder.group({
recipeDetails: this.formBuilder.group({
title: ["", Validators.required],
imageUrl: ["", Validators.required],
duration: ["", Validators.required],
calories: ["", Validators.required],
}),
ingredients: this.formBuilder.array([this.createIngFormGroup()]),
});
this.recipesService.fetchRecipeDetails(this.recipeId).subscribe(
(selectedRecipeDetails) => {
this.selectedRecipe = selectedRecipeDetails;
this.editRecipeF.patchValue({
recipeDetails: {
title: this.selectedRecipe.title,
imageUrl: this.selectedRecipe.imageUrl,
duration: this.selectedRecipe.duration,
calories: this.selectedRecipe.calories,
},
});
const ing = this.editRecipeF.get("ingredients") as FormArray;
for (let ingredient of this.selectedRecipe.ingredients) {
ing.patchValue([
{
name: ingredient.ingName,
qty: ingredient.ingQty,
qtyUnit: ingredient.ingQtyUnit,
imageUrl: ingredient.ingImageUrl,
},
]);
}
},
(error) => {
console.log(error);
}
);
}
private createIngFormGroup() {
return new FormGroup({
name: new FormControl("", Validators.required),
qty: new FormControl("", Validators.required),
qtyUnit: new FormControl("", Validators.required),
imageUrl: new FormControl("", Validators.required),
});
}
public getControls() {
return (<FormArray>this.editRecipeF.get("ingredients")).controls;
}
public onAddIngredients() {
const ingredients = this.editRecipeF.get("ingredients") as FormArray;
ingredients.push(this.createIngFormGroup());
}
public onSubmitEditRecipeForm() {
if (this.editRecipeF.valid) {
console.log(this.editRecipeF.value);
this.recipesService
.editRecipe(this.editRecipeF.value, this.recipeId)
.subscribe(
(success) => {},
(error) => {
console.log(error);
}
);
}
}
}
您需要 FormArray 的每个元素的路径值
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,
},
]);
}
您创建了一个 FormArray,其中包含许多元素作为成分
const ing = this.editRecipeF.get("ingredients") as FormArray;
ing.clear(); //<--remove all elements of the formArray;
//create so many elements as increadients
for (let ingredient of this.selectedRecipe.ingredients) {
let i=ing.length;
ing.insert(this.createIngFormGroup()) //<--add an empty formGroup
//make the pathValue
ing.at(i).patchValue( //<--see the at(i) and remove the "["
{
name: ingredient.ingName,
qty: ingredient.ingQty,
qtyUnit: ingredient.ingQtyUnit,
imageUrl: ingredient.ingImageUrl,
},
);
}
但我们甚至可以让事情变得更好。 PathValue 使我们的 FormGroup 具有我们指示的值。这样我们就可以完美的做出一个唯一的pathValue。有些喜欢
this.editRecipeF.patchValue({
recipeDetails: {
title: ..,
imageUrl: ...,
...
},
ingredients:[
{
name:...
qty: ...
...
},
{
name:...
qty: ...
...
},
{
name:...
qty: ...
...
},
]
})
确实,当我们收到数据时,我们可以检查我们的 FormArray 中有多少元素,add/remove 是必要的,并使 pathValue
但是当我们有一个 FormArray 时的问题是它是必要的我们的 formArray 有比我们拥有的“成分”更多的元素(如果我们的数组更少,路径值不要't 添加元素)
我建议另一种方法。想象一下,您稍微更改了函数 createIngFormGroup()
以允许接收到的对象使用数据创建 formGroup。经典是
private createIngFormGroup(data:any=null) {
//if not pass data as argument create a data "on fly"
//with values by defect
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),
});
}
所以
this.createIngFormGroup() //you get a FormGroup with empty values
this.createIngFormGroup({ //you get a FormGroup with the values indicated
ingName:"sugar",
ingQty:20,
ingQtyUnit:'spoon',
ingImageUrl:'/images/sugar.jpg'
})
此外,我们可以创建一个函数,return 一个 formGroup 类型“editRecipeF”,其想法与使用我们的函数的想法相同。我再次使用 FromGroup,而不是 FormBuilder 来为我们的代码提供“连贯性”。如果我们使用 FormBuilder,我们应该在组件的所有功能中使用 formBuilder,否则不在 where
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())
})
}
看到两件重要的事情。
首先我们的“成分”的缺陷值,我们选择将是一个具有空值的元素的数组
FormArray的创建方式。我们的formArray是FormGroup的一个FormArray,所以我们把数组data.ingredients转换成一个formGroup的数组。这是“地图”品牌:
data.map(x=>this.createIngFormGroup() //--is an array of FormGroup
我们附在new FormArray(..our array of FormGroups..)
嗯,一切都为我们准备好了。我们可以在 ngOnInit
ngOnInit()
{
this.editRecipeF=this.createForm()
}
或者我们可以,在订阅中制作
this.recipesService.fetchRecipeDetails(this.recipeId).subscribe(
(res:any) => {
this.editRecipeF=this.createForm(res)
})
就这样了。
重要说明。当您收到成分数据时,变量是ìngName、ingQty、ingQtyUnit 和 ingImageUrl,但是 formArray 的元素是 name、qty、qtyUnit 和 imageUrl(没有“ing”)。我觉得你的表单数组元素的变量有前缀“ing”更好(所以更像是你的数据库的数据更好,否则你需要 transform with/without 前缀来调用你的API)