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

Angular中使用*ngFor循环表单仅获取单条数据的问题

Angular表单数据收集问题解决方案

问题描述

使用单个HTML Form,需根据房间数量及每间房的成人数量,通过*ngFor循环生成对应数量的表单(例如2间房各有2位成人时,需生成4组表单),目标是将所有表单数据存入数组。但实际操作中,即便填写了所有表单,FormGroup仅能获取到一条表单数据。

问题根源

  1. 重复渲染的<form>标签绑定了同一个ravelerDetails FormGroup,导致表单控件重复绑定,数据被覆盖
  2. 错误地将formgroupname用于FormArray类型的customer_detail,FormArray需使用formArrayName指令配合索引绑定
  3. initDos方法复用了全局的salutation、first_name、last_name FormControl实例,所有表单行共享同一控件,最终只保留最后一次输入
  4. 未在表单中绑定room_number字段,无法区分不同房间的旅客

修正后的代码

HTML代码

<!-- 只保留一个外层form标签,避免重复绑定 -->
<form [formGroup]="ravelerDetails">
  <div *ngFor="let item of roomdetails; let roomIndex = index">
    <div class="row">
      <mat-card>
        <h2 class="titleHeader">ROOM {{roomIndex+1}}</h2>
        <!-- 绑定FormArray类型的customer_detail -->
        <div formArrayName="customer_detail">
          <!-- 遍历当前房间的成人,计算全局数组索引 -->
          <ng-container *ngFor="let adult of item.adult; let adultIndex = index; let globalIndex = roomIndex * item.adult.length + adultIndex">
            <!-- 绑定数组中对应的FormGroup -->
            <div [formGroupName]="globalIndex">
              <!-- 隐藏字段存储房间号 -->
              <input type="hidden" formControlName="room_number" [value]="roomIndex+1">
              <mat-form-field appearance="outline" color="primary" class="col-md-2">
                <mat-label>Salutation</mat-label>
                <mat-select formControlName="salutation">
                  <mat-option value="Mr">Mr.</mat-option>
                  <mat-option value="Mrs">Mrs.</mat-option>
                </mat-select>
              </mat-form-field>
              <mat-form-field appearance="outline" color="primary" class="col-md-5">
                <mat-label>First Name</mat-label>
                <input matInput placeholder="First Name" formControlName="first_name" maxlength="25">
                <mat-error *ngIf="ravelerDetails.get('customer_detail')['controls'][globalIndex].get('first_name')?.hasError('required')">
                  <strong>First Name</strong> is required.
                </mat-error>
              </mat-form-field>
              <mat-form-field appearance="outline" color="primary" class="col-md-5">
                <mat-label>Last Name</mat-label>
                <input matInput placeholder="Last Name" formControlName="last_name" maxlength="25">
                <mat-error *ngIf="ravelerDetails.get('customer_detail')['controls'][globalIndex].get('last_name')?.hasError('required')">
                  <strong>Last Name</strong> is required.
                </mat-error>
              </mat-form-field>
            </div>
          </ng-container>
        </div>
      </mat-card>
    </div>
  </div>
  <!-- 全局字段隐藏控件 -->
  <div style="display:none">
    <input formControlName="checkin_date">
    <input formControlName="checkout_date">
    <input formControlName="no_adult">
    <input formControlName="no_children">
    <input formControlName="no_room">
  </div>
</form>

TypeScript代码

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

// 组件其他代码...

// 初始化单个旅客表单组,每次创建新控件实例
initDos(roomNumber: string): FormGroup {
  return this.fb.group({
    room_number: [roomNumber],
    salutation: ['', Validators.required],
    first_name: ['', Validators.required],
    last_name: ['', Validators.required],
  });
}

// 初始化整个旅客详情表单
travellDetiles() {
  const customerDetailsArray = this.fb.array([]);
  
  // 按房间遍历,为每个成人生成表单组
  this.roomdetails.forEach((room, roomIndex) => {
    room.adult.forEach(() => {
      customerDetailsArray.push(this.initDos((roomIndex + 1).toString()));
    });
  });

  this.ravelerDetails = this.fb.group({
    customer_detail: customerDetailsArray,
    checkin_date: [this.hotelSearch.checkin_date],
    checkout_date: [this.hotelSearch.checkout_date],
    no_adult: [this.hotelSearch.no_adult],
    no_children: [this.hotelSearch.no_children],
    no_room: [this.hotelSearch.no_room],
  });
}

// 获取customer_detail表单数组
get dosArray() {
  return this.ravelerDetails.get('customer_detail') as FormArray;
}

// 动态添加旅客(需传入对应房间号)
dos(roomNumber: string) {
  this.dosArray.push(this.initDos(roomNumber));
}

// 删除指定索引的旅客表单
removeDos(index: number) {
  this.dosArray.removeAt(index);
}

期望输出

{
  "no_room": "1",
  "checkin_date": "2022-11-5",
  "checkout_date": "2022-11-6",
  "no_adult": "2",
  "customer_detail": [
    {
      "room_number": "1",
      "salutation": "Mr",
      "first_name": "test",
      "last_name": "test"
    },
    {
      "room_number": "1",
      "salutation": "Mr",
      "first_name": "test2",
      "last_name": "test2"
    }
  ]
}

内容的提问来源于stack exchange,提问作者vinita suthar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 23:45:42