I'm trying to create the dynamic form using FormArray and angular material table.
Template:
<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>
component:
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);
}
}
When I click the edit (pencil) button to edit the data wherever row I clicked, the data always fill with row number 1 even push the "Add" button to a new row.
my snippet code on stackblitz
CodePudding user response:
This line was never iterated. Hence the rowIndex
will be zero and it generates only one FormGroup
with the first record in FormArray
.
<div *ngFor="let tableRow of tableRowArray.controls; let rowIndex = index" [formGroupName]="rowIndex">
...
</div>
Solution
Instead, you have to get the index
from mat-cell
so that it iterates to generate each FormGroup
.
<td mat-cell *matCellDef="let row; let rowIndex = index" [formGroupName]="rowIndex">
...
</td>
The completed <form>
mat-table
elements should be as below:
<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>