如何在Angular中用*ngFor实现动态表单?技能矩阵数据提交问题
解决技能矩阵系统中为每个问题生成Answer模型并提交的问题
你找的方向没错——用FormArray管理每个问题的表单控件,再生成对应的Answer模型,但代码里有几个关键问题导致功能没跑通,我来帮你一步步修正:
核心问题分析
- FormArray构建时机错误:你在
constructor里调用buildForm(),但此时questions还没从后端拿到数据,导致FormArray是空的,根本没绑定上问题。 - 混合表单模式冲突:同时用了响应式表单(FormGroup/FormArray)和模板驱动表单(ngModel),两种模式的状态没法同步,评分数据自然没法正确关联到表单。
- Answer模型未与表单绑定:没有把用户选的skillLevel、energyLevel和每个问题的SkillID、当前日期关联起来生成Answer实例。
修正后的TS代码
import { Component, OnInit, OnDestroy, Input } from '@angular/core'; import { FormBuilder, FormGroup, FormArray, FormControl, Validators } from '@angular/forms'; import { Subscription } from 'rxjs'; import { QuestionService } from './question.service'; // 确保路径正确 import { Question } from './question.model'; // 确保路径正确 import { Answer } from './answer.model'; // 确保路径正确 export class QuestionListComponent implements OnInit, OnDestroy{ questions: Question[] = []; private questionsSub!: Subscription; thisIsMyForm: FormGroup; @Input() Capability: string; constructor( public questionsService: QuestionService, private formBuilder: FormBuilder ){ // 初始化表单结构 this.thisIsMyForm = this.formBuilder.group({ formArrayName: this.formBuilder.array([]) }); } // 获取FormArray的快捷方法,简化模板里的调用 get formArray(): FormArray { return this.thisIsMyForm.get('formArrayName') as FormArray; } buildForm(){ // 先清空已有控件,避免重复添加 this.formArray.clear(); this.questions.forEach(question => { // 只为匹配当前Capability的问题创建表单组 if(question.Capability === this.Capability){ this.formArray.push( this.formBuilder.group({ SkillID: [question.SkillID, Validators.required], skillLevel: [null, Validators.required], // 绑定技能等级 energyLevel: [null, Validators.required], // 绑定能量等级 date: [new Date(), Validators.required] // 默认填充当前日期 }) ); } }); } ngOnInit(){ this.questionsSub = this.questionsService.getQuestions().subscribe( data => { this.questions = Object.values(data); // 简化对象转数组的写法 this.buildForm(); // 拿到数据后再构建表单,确保有问题可绑定 console.log(this.questions); } ); } // 生成Answer模型数组并提交到数据库 onSubmit(){ if(this.thisIsMyForm.invalid) return; // 从FormArray中提取数据,生成对应的Answer实例 const answerModels: Answer[] = this.formArray.controls.map(control => { const formValue = control.value; // 这里假设Answer构造函数参数顺序是(date, score, skillID, userID) // 注意:UserID需要你从登录状态/localStorage等地方获取,这里用占位符1代替 return new Answer( formValue.date, formValue.skillLevel, // 如果score是skill+energy的组合,这里改成对应的计算逻辑 formValue.SkillID, 1 // 替换为实际的用户ID ); }); // 调用服务提交数据到数据库 // this.questionsService.submitAnswers(answerModels).subscribe( // res => console.log('提交成功'), // err => console.error('提交失败', err) // ); console.log('待提交的Answer模型:', answerModels); } // 通过SkillID匹配对应的问题对象,给模板用 getQuestionBySkillID(skillID: number): Question | undefined { return this.questions.find(q => q.SkillID === skillID); } ngOnDestroy() { if(this.questionsSub) this.questionsSub.unsubscribe(); } }
修正后的HTML代码
<form id="myForm" [formGroup]="thisIsMyForm" (ngSubmit)="onSubmit()"> <div formArrayName="formArrayName"> <!-- 遍历FormArray里的每个表单控件 --> <ul *ngFor="let control of formArray.controls; let i = index"> <div [formGroupName]="i"> <!-- 通过SkillID匹配对应的问题数据 --> <ng-container *ngIf="getQuestionBySkillID(control.value.SkillID) as question"> <h3 class="SubCat">{{question.SubCategory}} {{question.Skill}}</h3> <div class="EandS"> <h4 class="skill">Skill</h4> <h4 class="energy">Energy</h4> </div> <div class="buttons"> <div class="skillButtons"> <mat-radio-group aria-label="Select your Skill Level" formControlName="skillLevel" > <mat-radio-button [value]="0" class="rbuttonSkill">0</mat-radio-button> <mat-radio-button [value]="1" class="rbuttonSkill">1</mat-radio-button> <mat-radio-button [value]="2" class="rbuttonSkill">2</mat-radio-button> <mat-radio-button [value]="3" class="rbuttonSkill">3</mat-radio-button> <mat-radio-button [value]="4" class="rbuttonSkill">4</mat-radio-button> </mat-radio-group> </div> <div class="energyButtons"> <mat-radio-group aria-label="Select your Energy Level" formControlName="energyLevel" > <mat-radio-button [value]="1" class="rbuttonEnergy">1</mat-radio-button> <mat-radio-button [value]="2" class="rbuttonEnergy">2</mat-radio-button> <mat-radio-button [value]="3" class="rbuttonEnergy">3</mat-radio-button> </mat-radio-group> </div> </div> <div class="Selected answer"> <strong>Selected Skill Level: {{control.value.skillLevel}} (SkillID: {{question.SkillID}})</strong> <br> <strong>Selected Energy Level: {{control.value.energyLevel}} (SkillID: {{question.SkillID}})</strong> </div> </ng-container> </div> </ul> </div> <!-- 表单未填完时禁用提交按钮 --> <button type="submit" [disabled]="thisIsMyForm.invalid" mat-raised-button color="accent">Save</button> </form> <!-- 可选:调试用的表单值展示 --> <pre>{{ thisIsMyForm.value | json }}</pre>
关键修改说明
- 表单构建时机调整:把
buildForm()移到getQuestions()的订阅回调里,确保拿到问题数据后再创建表单控件。 - 统一使用响应式表单:移除了模板驱动的
ngModel和ngForm,所有控件都绑定到FormArray的FormGroup里,状态同步更可靠。 - Answer模型生成逻辑:在提交时遍历FormArray的控件,把每个控件的值转换成Answer实例,直接就能批量提交到数据库。
- 表单验证:给每个必填控件加了
Validators.required,提交按钮会自动禁用直到所有评分都选完。
这样修改后,你就能正确为每个问题生成对应的Answer模型,并且一次性把所有数据提交到数据库了。
内容的提问来源于stack exchange,提问作者Luke Harland
相关产品推荐
相关产品推荐

