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
相关产品推荐
相关产品推荐

