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

Angular FormArray结合Material Table开发动态表单时编辑行数据异常如何解决?

问题原因
  • 模板结构错误:你在列定义matColumnDef内部嵌套了遍历所有tableRowArray控件的循环,导致每个单元格都会重复生成所有行的表单控件,Angular在匹配formControlName时会默认取第一个匹配的控件,也就是永远对应第一行数据。
  • 行索引未绑定:没有将Material Table的当前行索引和FormArray的索引做关联,表单控件无法匹配到对应的行数据。
  • 删除行逻辑不完整:现有removeRow方法只删除了数据源DetailDS的元素,没有同步删除tableRowArray中对应的表单组,会导致后续行索引和表单索引错位。
  • 初始数据缺少状态字段:初始的DATA_DETAIL没有添加isEdit字段,导致初始编辑状态判断异常。
解决方案

1. 模板代码修改

调整表单嵌套结构,删除列内部的无效行循环,绑定当前行索引:

<form [formGroup]="myFormDetail" id="formDetail">
  <div formArrayName="tableRowArray">
    <table class="detail-table" mat-table [dataSource]="DetailDS">
      <ng-container *ngFor="let column of columns" [matColumnDef]="column.property">
        <ng-container *ngIf="column.isProperty">
          <th mat-header-cell *matHeaderCellDef>
            <span *ngIf="(column.label !== 'Edit') && (column.label !== 'Select')"> {{ column.label }}</span>
            <span *ngIf="column.label === 'Select'">
              <mat-checkbox aria-label="Select All"
                            [checked]="isChecked()"
                            [indeterminate]="isIndeterminate()"
                            (change)="$event ? isAllSelected($event) : null"></mat-checkbox>
            </span>
          </th>

          <td mat-cell *matCellDef="let row; let rowIndex = index" [formGroupName]="rowIndex">
            <div *ngIf="!row.isEdit">
              <div *ngIf="column.label === 'Select'">
                <mat-checkbox (click)="$event.stopPropagation()"
                              (change)="$event ? toggle(row, $event) : null;"
                              [checked]="exists(row)">
                </mat-checkbox>
              </div>
              <div  *ngIf="column.label === 'Edit'; spanHeader">
                <div class="input-group">
                  <button class="btn btn-xs btn-outline-success" (click)="row.isEdit = !row.isEdit">
                    <i class="bi bi-pencil"></i>
                  </button>
                  <button class="btn btn-xs btn-outline-danger" (click)="removeRow(row.id)">
                    <i class="bi bi-x"></i>
                  </button>
                </div>
              </div>
              <span #spanHeader>{{ row[column.property] }}</span>
            </div>

            <div [ngSwitch]="dataSchema[column.label]" *ngIf="row.isEdit">
              <div *ngSwitchCase="'select'"> {{ rowIndex }}</div>

              <div *ngSwitchCase="'edit'">
                <div class="input-group">
                  <button class="btn btn-xs btn-outline-success" (click)="row.isEdit = !row.isEdit;">
                    <i class="bi bi-save"></i>
                  </button>
                  <button class="btn btn-xs btn-outline-primary" (click)="row.isEdit = !row.isEdit; removeRow(row.id, row)">
                    <i class="bi bi-arrow-counterclockwise"></i>
                  </button>
                </div>
              </div>

              <mat-form-field *ngSwitchDefault [style.width.%]="inherit">
                <mat-label>{{ column.label }}</mat-label>
                <input matInput [formControlName]="column.property"
                style="width: inherit !important">
              </mat-form-field>
            </div>
          </td>
        </ng-container>
      </ng-container>

      <tr mat-header-row *matHeaderRowDef="visibleColumns"></tr>
      <tr mat-row *matRowDef="let row; let rowIndex = index; columns: visibleColumns" ></tr>
    </table>
  </div>
</form>

核心修改点:

  • 把formArrayName的div移到table外层,避免嵌套错误
  • 删除列定义内部的*ngFor行循环
  • 在*matCellDef中获取当前行索引rowIndex,直接绑定[formGroupName]="rowIndex"

2. 组件代码修改

2.1 初始数据补充状态字段

给DATA_DETAIL的每条初始数据添加isEdit: false:

const DATA_DETAIL = [
  { id: 1, coa: '1111', description: 'Uraian', dc: 'D', currency: 'IDR', amount: 12345, local_amount: 12345, cross_coa: '2222', isEdit: false },
  { id: 2, coa: '2222', description: 'Uraian', dc: 'D', currency: 'IDR', amount: 12345, local_amount: 12345, cross_coa: '1111', isEdit: false },
  { id: 3, coa: '3333', description: 'Uraian', dc: 'D', currency: 'IDR', amount: 12345, local_amount: 12345, cross_coa: '4444', isEdit: false },
];

2.2 完善删除行逻辑,同步操作表单数组

修改removeRow方法:

removeRow(id, row?) {
  console.log('255: Idx: ' + id);
  console.log('256: Row: ' + JSON.stringify(row));
  console.log('265: DetailDS: ' + JSON.stringify(this.DetailDS));

  var remove = row === undefined || row.isNew ? true : row.isEdit;
  if (remove) {
    // 查找要删除的行索引
    const delIndex = this.DetailDS.findIndex(u => u.id === id);
    if (delIndex > -1) {
      this.DetailDS = this.DetailDS.filter((u) => u.id !== id);
      // 同步删除FormArray对应的表单组
      this.tableRowArray.removeAt(delIndex);
    }
  }
  console.log('265: DetailDS: ' + JSON.stringify(this.DetailDS));
}

修改removeSelectedRow方法:

removeSelectedRow() {
  // 从后往前删除避免索引错位
  for (let i = this.DetailDS.length - 1; i >= 0; i--) {
    if (this.DetailDS[i].selected) {
      this.DetailDS.splice(i, 1);
      this.tableRowArray.removeAt(i);
    }
  }
  this.DetailDS = [...this.DetailDS];
  this.selection = this.selection.filter((u: any) => !u.selected);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 17:48:01