You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Angular中用*ngFor实现动态表单?技能矩阵数据提交问题

解决技能矩阵系统中为每个问题生成Answer模型并提交的问题

你找的方向没错——用FormArray管理每个问题的表单控件,再生成对应的Answer模型,但代码里有几个关键问题导致功能没跑通,我来帮你一步步修正:

核心问题分析

  1. FormArray构建时机错误:你在constructor里调用buildForm(),但此时questions还没从后端拿到数据,导致FormArray是空的,根本没绑定上问题。
  2. 混合表单模式冲突:同时用了响应式表单(FormGroup/FormArray)和模板驱动表单(ngModel),两种模式的状态没法同步,评分数据自然没法正确关联到表单。
  3. 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>

关键修改说明

  1. 表单构建时机调整:把buildForm()移到getQuestions()的订阅回调里,确保拿到问题数据后再创建表单控件。
  2. 统一使用响应式表单:移除了模板驱动的ngModel和ngForm,所有控件都绑定到FormArray的FormGroup里,状态同步更可靠。
  3. Answer模型生成逻辑:在提交时遍历FormArray的控件,把每个控件的值转换成Answer实例,直接就能批量提交到数据库。
  4. 表单验证:给每个必填控件加了Validators.required,提交按钮会自动禁用直到所有评分都选完。

这样修改后,你就能正确为每个问题生成对应的Answer模型,并且一次性把所有数据提交到数据库了。

内容的提问来源于stack exchange,提问作者Luke Harland

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 06:47:22