如何在Angular中构建复杂模型对应表单并将数据写入Firestore
我正在构建一个人力资源管理系统,采用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:已补充更多细节
解决方案
你可以通过 Angular 响应式表单的 FormArray 实现动态福利项管理,提交时再将表单数据转换为适配你 Contract 模型的结构即可,无需修改现有服务层逻辑,具体实现如下:
1. 组件逻辑实现
import { ContractService } from '../services/contract.service'; import { Contract } from '../models/contract.model'; import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms'; export class ManageContractComponent implements OnInit { constructor( public contractService: ContractService,) { } newContract: FormGroup; modalActive: boolean = false; // 快捷获取福利项FormArray实例 get benefits(): FormArray { return this.newContract.get('benefitsAllowances') as FormArray; } ngOnInit(): void { this.newContract = new FormGroup( { 'contractID': new FormControl('', Validators.required), // 其他基础字段按你的业务需求补充即可 'benefitsAllowances': new FormArray([]) // 初始为空的福利数组 } ); } // 新增福利项 addBenefit() { const benefitGroup = new FormGroup({ benefitName: new FormControl('', Validators.required), pay: new FormControl('', Validators.required) }); this.benefits.push(benefitGroup); } // 删除指定索引的福利项 deleteBenefit(index: number) { this.benefits.removeAt(index); } // 提交表单逻辑 addContract() { const formValue = this.newContract.value; // 将FormArray转换为模型要求的键值对结构 const formattedBenefits = {}; formValue.benefitsAllowances.forEach(item => { formattedBenefits[item.benefitName] = { pay: item.pay }; }); // 组装为符合Contract模型的对象 const contractData: Contract = { ...formValue, benefitsAllowances: formattedBenefits }; // 直接调用现有服务提交即可 this.contractService.createContract(contractData); this.modalActive = false; // 提交后清空福利数组(可选) this.benefits.clear(); } // 其他原有逻辑如模态框开关等按你之前的实现保留即可 }
2. 模板部分实现
在原有表单结构基础上加入动态福利项区域即可:
<!-- 其他基础字段(contractID等)的表单项按需求放在前面 --> <!-- 动态福利项区域 --> <div class="field"> <label class="label">福利补贴</label> <div *ngFor="let benefit of benefits.controls; let i = index" [formGroup]="benefit" class="box" style="margin-bottom: 12px;"> <div class="field is-grouped"> <div class="control is-expanded"> <input formControlName="benefitName" type="text" class="input" placeholder="福利名称(如交通补贴、餐补等)"> </div> <div class="control is-expanded"> <input formControlName="pay" type="text" class="input" placeholder="补贴金额"> </div> <div class="control"> <button type="button" class="button is-danger" (click)="deleteBenefit(i)">删除</button> </div> </div> </div> <button type="button" class="button is-link is-outlined" (click)="addBenefit()">+ 新增福利项</button> </div> <!-- 原有提交按钮保持不变即可 -->
该实现完全兼容你现有的服务层逻辑,无需修改Firebase写入代码,即可支持用户自由添加/删除任意数量的福利项。
内容的提问来源于stack exchange,提问作者Kris Kamweru

