I'm trying to build an inline editable table using the latest material+cdk for angular.
How can I make mat-table use
[formGroupName]
so that the form fields can be referenced by its correct form path?
This is what I got so far: Complete StackBlitz example
Template
<form [formGroup]="form"> <h1>Works</h1> <div formArrayName="dates" *ngFor="let date of rows.controls; let i = index;"> <div [formGroupName]="i"> <input type="date" formControlName="from" placeholder="From date"> <input type="date" formControlName="to" placeholder="To date"> </div> </div> <h1>Wont work</h1> <table mat-table [dataSource]="dataSource" formArrayName="dates"> <!-- Row definitions --> <tr mat-header-row *matHeaderRowDef="displayColumns"></tr> <tr mat-row *matRowDef="let row; let i = index; columns: displayColumns;" [formGroupName]="i"></tr> <!-- Column definitions --> <ng-container matColumnDef="from"> <th mat-header-cell *matHeaderCellDef> From </th> <td mat-cell *matCellDef="let row"> <input type="date" formControlName="from" placeholder="From date"> </td> </ng-container> <ng-container matColumnDef="to"> <th mat-header-cell *matHeaderCellDef> To </th> <td mat-cell *matCellDef="let row"> <input type="date" formControlName="to" placeholder="To date"> </td> </ng-container> </table> <button type="button" (click)="addRow()">Add row</button> </form>
Component
export class AppComponent implements OnInit { data: TableData[] = [ { from: new Date(), to: new Date() } ]; dataSource = new BehaviorSubject<AbstractControl[]>([]); displayColumns = ['from', 'to']; rows: FormArray = this.fb.array([]); form: FormGroup = this.fb.group({ 'dates': this.rows }); constructor(private fb: FormBuilder) { } ngOnInit() { this.data.forEach((d: TableData) => this.addRow(d, false)); this.updateView(); } emptyTable() { while (this.rows.length !== 0) { this.rows.removeAt(0); } } addRow(d?: TableData, noUpdate?: boolean) { const row = this.fb.group({ 'from' : [d && d.from ? d.from : null, []], 'to' : [d && d.to ? d.to : null, []] }); this.rows.push(row); if (!noUpdate) { this.updateView(); } } updateView() { this.dataSource.next(this.rows.controls); } }
This wont work. Console yields
ERROR Error: Cannot find control with path: 'dates -> from'
It seems as if the [formGroupName]="i"
has no effect, cause the path should be dates -> 0 -> from
when using a formArray.
My current workaround: For this problem, I've bypassed the internal path lookup (formControlName="from"
) and use the form control directly: [formControl]="row.get('from')"
, but I would like to know how I can (or at least why I cannot) use the Reactive Form preferred way.
Any tips are welcome. Thank you.
Since I think this is a bug, I've registered an issue with the angular/material2 github repo.
I would use the index which we can get within matCellDef
binding:
*matCellDef="let row; let index = index" [formGroupName]="index"
Forked Stackblitz
For solving problems with sorting and filtering take a look at this answer Angular Material Table Sorting with reactive formarray
here is the sample code
In Html:
<form [formGroup]="tableForm"> <mat-table formArrayName="users" [dataSource]="dataSource"> <ng-container cdkColumnDef="position"> <mat-header-cell *cdkHeaderCellDef> No. </mat-header-cell> <mat-cell *cdkCellDef="let row let rowIndex = index" [formGroupName]="rowIndex"> <input type="text" size="2" formControlName="position"> </mat-cell> </ng-container> <ng-container cdkColumnDef="name"> <mat-header-cell *cdkHeaderCellDef> Name </mat-header-cell> <mat-cell *cdkCellDef="let row let rowIndex = index" [formGroupName]="rowIndex"> <input type="text" size="7" formControlName="name"> </mat-cell> </ng-container> <ng-container cdkColumnDef="weight"> <mat-header-cell *cdkHeaderCellDef> Weight </mat-header-cell> <mat-cell *cdkCellDef="let row let rowIndex = index" [formGroupName]="rowIndex"> <input type="text" size="3" formControlName="weight"> </mat-cell> </ng-container> <ng-container cdkColumnDef="symbol"> <mat-header-cell *cdkHeaderCellDef> Symbol </mat-header-cell> <mat-cell *cdkCellDef="let row let rowIndex = index" [formGroupName]="rowIndex"> <input type="text" size="2" formControlName="symbol"> </mat-cell> </ng-container> <!-- Header and Row Declarations --> <mat-header-row *cdkHeaderRowDef="displayedColumns"></mat-header-row> <mat-row *cdkRowDef="let row; columns: displayedColumns;"></mat-row> </mat-table> </form>
Controller code:
displayedColumns: string[] = ['position', 'name', 'weight', 'symbol']; dataSource ; tableForm: FormGroup; constructor(private formBuilder: FormBuilder){ this.dataSource = [ {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'}, {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'}, {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'}, {position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'}, {position: 5, name: 'Boron', weight: 10.811, symbol: 'B'}, {position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'}, {position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'}, {position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'}, {position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'}, {position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'}, ]; } ngOnInit(){ this.tableForm= this.formBuilder.group({ users: this.formBuilder.array([]) }) this.setUsersForm(); this.tableForm.get('users').valueChanges.subscribe(users => {console.log('users', users)}); } private setUsersForm(){ const userCtrl = this.tableForm.get('users') as FormArray; this.dataSource.forEach((user)=>{ userCtrl.push(this.setUsersFormArray(user)) }) }; private setUsersFormArray(user){ return this.formBuilder.group({ position:[user.position], name:[user.name], weight:[user.weight], symbol:[user.symbol] }); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With