如何构建表单以获取 angular 中复杂模型的数据,然后将该数据写入 firestore?

How to build a form to get data for a complex model in angular, for the purpose of then writing that data to firestore?

前言

所以我正在构建一个系统来管理一些 HR 的东西,我正在使用 Cloud Firestore 作为我的后端。就我正在处理的数据而言,我正在使用模型来定义我需要的内容(下面的示例模型),然后根据这些模型制作表格以从用户那里收集数据。然后我将所述数据推送到 firebase。对于我的大多数模型,数据都非常简单,因此我可以轻松完成。 例如,我有 leave.model.ts:

export class Leave {
    employeeID: string;
    organizationID: string;
    annualLeaveDays: number;
    maternityLeaveDays: number;
    paternityLeaveDays: number;
    sickLeaveDays: number;
    unpaidLeaveDays: number;
}

然后我用它来为 CRUD 创建服务,leave.service.ts:

import { AngularFirestore } from '@angular/fire/firestore';
import { Leave } from '../models/leave.model';

export class LeaveService {

  constructor(
    private firestore: AngularFirestore,
  ) {}

  createLeave(leave: Leave) {
    return this.firestore.collection('orgData').doc(leave.organizationID).collection('leaveDetails').doc(leave.employeeID).set(leave);
  }
}

然后,当我构建表单以收集数据并将数据推送到 firebase 时,我会在我的组件中使用此服务:
leave.component.ts

import { LeaveService } from '../services/leave.service';
import { Leave } from '../models/leave.model';

import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms';

export class ManageLeaveComponent implements OnInit {

  constructor(
    public leaveService: LeaveService,) {
  }

  newLeave: FormGroup;

  modalActive: boolean = false;

  Leaves: Leave[];

  ngOnInit(): void {
    this.newLeave = new FormGroup(
      {
        'employeeID': new FormControl('', Validators.required),
        'organizationID': new FormControl('', Validators.required),
        'annualLeaveDays': new FormControl('', Validators.required),
        'maternityLeaveDays': new FormControl('', Validators.required),
        'paternityLeaveDays': new FormControl('', Validators.required),
        'sickLeaveDays': new FormControl('', Validators.required),
        'unpaidLeaveDays': new FormControl('', Validators.required),
      }
    );
  }

  newLeaveClick(){
    this.modalActive = true;
  }

  addLeave() {
    this.leaveService.createLeave(this.newLeave.value);
    this.modalActive = false;
  }
}

leave.component.html

<div class="container">
    <div class="buttonContainer">
        <button class="button is-primary" (click) = "newLeaveClick()">Add Leave Details</button>
    </div>
</div>

<div class="modal" [class.is-active] = "modalActive">
    <div class="modal-background" (click) = "modalClose()"></div>
    <div class="modal-content">
        <!-- Form for collecting new leave information -->
        <div class="box">
            <form [formGroup]="newLeave" (ngSubmit)="addLeave()">

                <div class="field">
                    <select
                    id = "tableSelect"
                    formControlName="organizationID"
                    (change)="getEmployees()"
                    [(ngModel)]="selectedOrganization">
                        <option
                        *ngFor = "let organization of Orgs"
                        [value] = "organization.organizationID" >{{ organization.organizationName }}</option>
                    </select>
                </div>

                <div class="field">
                    <select
                    id = "tableSelect"
                    formControlName="employeeID"
                    [(ngModel)]="selectedEmployee">
                        <option
                        *ngFor = "let employee of Employees"
                        [value] = "employee.id" >{{ employee.name }}</option>
                    </select>
                </div>

                <div class="field">
                    <label class="label">Annual Leave Days</label>
                    <input formControlName="annualLeaveDays" type="text" class="input">
                </div>
                <div class="field">
                    <label class="label">Maternity Leave Days</label>
                    <input formControlName="maternityLeaveDays" type="text" class="input">
                </div>
                <div class="field">
                    <label class="label">Paternity Leave Days</label>
                    <input formControlName="paternityLeaveDays" type="text" class="input">
                </div>
                <div class="field">
                    <label class="label">Sick Leave Days</label>
                    <input formControlName="sickLeaveDays" type="text" class="input">
                </div>
                <div class="field">
                    <label class="label">Unpaid Leave Days</label>
                    <input formControlName="unpaidLeaveDays" type="text" class="input">
                </div>
                

                <div class="buttonContainer">
                    <button class="button is-primary" type="submit">Add Leave Details</button>
                </div>
            </form>
        </div>
    </div>
</div>

还有一些额外的功能,但为了简洁起见,我将它们省略了

问题

我遇到的问题是基于一个不那么简单的模型创建反应式表单:
contract.model.ts

export class Contract {
    contractID: string;
    // other basic details

    //this bit below is the complicated part. I need it to have an array that holds any number of benefits, depending on the contract details. The model is fine, but building a form to interact with it is what I don't get
    benefitsAllowances: {
        [benefitName: string]: {
            pay: string,
        }
    };
}

正如我现在设置的那样,该服务的工作方式与之前所示的相同。它需要一个 Contract 类型的对象,然后它将用于写入 firebase。
但是,我终其一生都无法弄清楚如何使用 formGroup 和 formArray 为此收集数据,包括让用户根据需要向合同添加尽可能多的好处的能力。
我假设在 HTML 方面我会使用 ngFor 指令在添加时动态显示好处,但我不知道如何添加它自己

如果您需要更具体的信息,请告诉我。 提前致谢!

编辑 1: 包括更多细节

有多种方法可以解决这个问题,下面是一个示例,其中包含一个修改后的模型以降低代码复杂性。

我把你的模型修改成这样:

export interface Contract {
  contractID: string;
  // other basic details

  benefitsAllowances: Benefit[]; // modified to hold an array of benefits
}

export interface Benefit {
  name: string;
  pay: string;
}

我没有使用福利名称作为键,而是将其添加为一个字段并为 benefitsAllowances 创建了一个福利数组。这允许我们在更新好处时降低代码复杂性(使用密钥作为表单组名称将需要我们在每次更改名称时删除并创建一个新的表单组)。

您的新组件将如下所示:

import {Component, OnInit} from '@angular/core';
import {FormArray, FormBuilder, FormGroup, FormGroupDirective, NgForm, Validators} from "@angular/forms";


@Component({
  selector: 'app-form-array',
  templateUrl: './form-array.component.html',
  styleUrls: ['./form-array.component.scss']
})
export class FormArrayComponent implements OnInit {

  // form variables
  form: FormGroup = new FormGroup({});
  benefitsAllowances: FormArray = new FormArray([]);

  constructor(private formBuilder: FormBuilder) {
  }

  ngOnInit(): void {
    this.form = this.formBuilder.group({
      contractID: ['default id'],
      benefitsAllowances: this.formBuilder.array([])
    });
  }


  addItem(name: string, pay: string) {
    this.benefitsAllowances = this.form.get('benefitsAllowances') as FormArray;
    this.benefitsAllowances.push(
      this.formBuilder.group(
        {
          name: name,
          pay: pay
        } as Benefit
      ));
  }

  submit(form: FormGroup) {
    console.log(form.getRawValue());
  }
}

我所做的是声明一个类型为 FormArray 的变量 benefitsAllowances。每次我们想要添加一个新的好处时,都会调用 addItem() 函数,这将向数组添加另一个好处。

HTML模板如下:

<h3>Add new Benefit</h3>
<form (ngSubmit)="addItem(name.value, pay.value); name.value = ''; pay.value = '';">
  <label for="benefit">Benefit Name:</label>
  <input #name id="benefit" type="text">

  <label for="benefit-pay">Benefit Pay:</label>
  <input #pay id="benefit-pay" type="number">
  <button>Add Item</button>
</form>


<h3>Current Benefits</h3>
<form [formGroup]="form" (ngSubmit)="submit(form)">
  <div
    formArrayName="benefitsAllowances"
    *ngFor="let benefit of benefitsAllowances.controls; let i = index;">

    <div [formGroupName]="i">
      <label for="benefit-{{ i }}">Benefit Name:</label>
      <input id="benefit-{{ i }}" type="text" formControlName="name">

      <label for="benefit-pay-{{ i }}">Benefit Pay:</label>
      <input id="benefit-pay-{{ i }}" type="text" formControlName="pay">
    </div>
  </div>
  <button type="submit">Submit</button>
</form>

{{form.getRawValue() | json}}

当您填写表格并添加新福利时,它会添加到您的表单组并通过 ngFor 循环显示,然后您可以在其中添加其他福利或编辑现有福利,这要归功于两个- 方式数据绑定。

提交后,您将能够将这些好处作为数组上传到 Firestore。

这里有一个 stackblitz 的现场演示