如何为数组中的每个对象动态创建 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)