Angular - 带有材料表和数据行的 FormArray 不正确
Posted
技术标签:
【中文标题】Angular - 带有材料表和数据行的 FormArray 不正确【英文标题】:Angular - FormArray with Material Table and data row was incorrect 【发布时间】:2021-11-16 11:42:41 【问题描述】:我正在尝试使用 FormArray 和角度材料表创建动态表单。
模板:
<div class="container-fluid col-md-12">
<div class="detail-container mat-elevation-z8" style="border-radius: 5px !important;">
<div class="d-flex float-md-end" style="padding: 10px;">
<div class="input-group">
<button class="btn btn-sm btn-outline-success" (click)="addRow()">
<i class="bi bi-file-earmark-plus"></i> Add
</button>
<button class="btn btn-sm btn-outline-danger" (click)="removeSelectedRow()">
<i class="bi bi-file-earmark-minus"></i> Remove
</button>
</div>
</div>
<!-- -- Here to be customized -->
<form [formGroup]="myFormDetail" id="formDetail">
<table class="detail-table" mat-table [dataSource]="DetailDS">
<div formArrayName="tableRowArray">
<ng-container *ngFor="let column of columns" [matColumnDef]="column.property">
<div *ngFor="let tableRow of tableRowArray.controls; let rowIndex = index" [formGroupName]="rowIndex">
<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">
<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">
<!-- -- Just showing the rowIndex debugging purpose -->
<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 *ngSwitchCase="'currency'">
<ng-container [ngTemplateOutlet]="tCurrency"></ng-container>
</mat-form-field> -->
<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>
</div>
</ng-container>
</div>
<tr mat-header-row *matHeaderRowDef="visibleColumns"></tr>
<tr mat-row *matRowDef="let row; columns: visibleColumns" ></tr>
</table>
</form>
<pre><small>
myFormDetail?.value | json
</small>
</pre>
</div>
</div>
组件:
export class ListColumns
label?: string;
property?: string;
visible?: boolean;
isProperty?: boolean;
type?: string;
inlineEdit?: boolean;
const DATA_SCHEMA =
id: 'number',
coa: 'text',
description: 'text',
dc: 'text',
currency: 'currency',
amount: 'number',
local_amount: 'number',
cross_coa: 'text',
Edit: 'edit',
Select: 'select',
;
const DATA_DETAIL = [
id: 1, coa: '1111', description: 'Uraian', dc: 'D', currency: 'IDR', amount: 12345, local_amount: 12345, cross_coa: '2222' ,
id: 2, coa: '2222', description: 'Uraian', dc: 'D', currency: 'IDR', amount: 12345, local_amount: 12345, cross_coa: '1111' ,
id: 3, coa: '3333', description: 'Uraian', dc: 'D', currency: 'IDR', amount: 12345, local_amount: 12345, cross_coa: '4444' ,
];
@Component(
selector: 'app-mattable-reactive',
templateUrl: './mattable-reactive.component.html',
styleUrls: ['./mattable-reactive.component.css'],
)
export class MattableReactiveComponent implements OnInit
// -- detail Part
dataSchema = DATA_SCHEMA;
DetailDS = DATA_DETAIL;
// DetailDS: any;
columns: ListColumns[] = [
label: 'Select', property: 'select', visible: true, isProperty: true ,
label: 'GL Accounts', property: 'coa', visible: true, isProperty: true ,
label: 'Description', property: 'description', visible: true, isProperty: true ,
label: 'D/C', property: 'dc', visible: true, isProperty: true ,
label: 'Currency', property: 'currency', visible: true, isProperty: true ,
label: 'Amount', property: 'amount', visible: true, isProperty: true ,
label: 'Local Amount', property: 'local_amount', visible: true, isProperty: true ,
label: 'Cross GL Account', property: 'cross_coa', visible: true, isProperty: true,
label: 'Edit', property: 'edit', visible: true, isProperty: true ,
];
selection = [];
myFormDetail: FormGroup;
constructor(private fb: FormBuilder)
ngOnInit()
this.createFormDetail();
this.getDetailRowData();
// -- Detail Part
createFormDetail()
this.myFormDetail = this.fb.group(
tableRowArray: this.fb.array([]),
);
createTableRow(detailDS): FormGroup
return this.fb.group(
id: new FormControl(detailDS.id),
coa: new FormControl(detailDS.coa),
description: new FormControl(detailDS.description),
dc: new FormControl(detailDS.dc),
currency: new FormControl(detailDS.currency),
amount: new FormControl(detailDS.amount),
local_amount: new FormControl(detailDS.local_amount),
cross_coa: new FormControl(detailDS.cross_coa),
edit: new FormControl(detailDS.edit),
select: new FormControl(detailDS.select),
);
getDetailRowData()
// const formArray = this.myFormDetail.get('tableRowArray') as FormArray;
this.DetailDS.map((item) =>
console.log('ITEM: ' + JSON.stringify(item));
this.tableRowArray.push(this.createTableRow(item));
);
this.myFormDetail.setControl('tableRowArray', this.tableRowArray);
console.log('221: DetailDS: ' + JSON.stringify(this.DetailDS));
get tableRowArray(): FormArray
return this.myFormDetail.get('tableRowArray') as FormArray;
get visibleColumns()
return this.columns
.filter((column) => column.visible)
.map((column) => column.property);
addRow()
const newRow = id: Math.floor(Date.now()), coa: '', description: '', dc: '', currency: '', amount: 0, local_amount: 0, cross_coa: '', isEdit: true, isNew: true ;
this.DetailDS = [...this.DetailDS, newRow];
this.tableRowArray.push(this.createTableRow(newRow));
console.log('273: DetailDS: ' + JSON.stringify(this.DetailDS));
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)
this.DetailDS = this.DetailDS.filter((u) => u.id !== id);
console.log('265: DetailDS: ' + JSON.stringify(this.DetailDS));
removeSelectedRow()
this.DetailDS = this.DetailDS.filter((u: any) => !u.selected);
this.selection = this.selection.filter((u: any) => !u.selected);
当我单击编辑(铅笔)按钮以编辑我单击的任何行的数据时,数据总是以第 1 行填充,甚至将“添加”按钮推到新行。
我在stackblitz上的sn-p代码
【问题讨论】:
【参考方案1】:此行从未迭代。因此rowIndex
将是零,它只生成一个FormGroup
,第一条记录在FormArray
。
<div *ngFor="let tableRow of tableRowArray.controls; let rowIndex = index" [formGroupName]="rowIndex">
...
</div>
解决方案
相反,您必须从mat-cell
获取index
,以便它迭代生成每个FormGroup
。
<td mat-cell *matCellDef="let row; let rowIndex = index" [formGroupName]="rowIndex">
...
</td>
完整的<form>
+ mat-table
元素应如下所示:
<form [formGroup]="myFormDetail" id="formDetail">
<table class="detail-table" mat-table [dataSource]="DetailDS">
<div formArrayName="tableRowArray">
<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">
<!-- -- Just showing the rowIndex debugging purpose -->
<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 *ngSwitchCase="'currency'">
<ng-container [ngTemplateOutlet]="tCurrency"></ng-container>
</mat-form-field> -->
<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>
</div>
<tr mat-header-row *matHeaderRowDef="visibleColumns"></tr>
<tr mat-row *matRowDef="let row; columns: visibleColumns"></tr>
</table>
</form>
Sample Solution on StackBlitz
【讨论】:
我相信您的原始代码没有完成,上面的解决方案刚刚解决了您当前的问题。您必须实现click
事件的逻辑才能更新表格。
谢谢...就是这样,我需要...接下来解决点击事件。以上是关于Angular - 带有材料表和数据行的 FormArray 不正确的主要内容,如果未能解决你的问题,请参考以下文章
带有分页的可扩展嵌套 Angular 材料数据表未按预期工作?