Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Material editable table using FormArray

I'm trying to build an inline editable table using the latest material+cdk for angular.

Question

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);   } } 

Problem

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.

like image 278
Øystein Amundsen Avatar asked Jul 03 '18 08:07

Øystein Amundsen


2 Answers

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

like image 132
yurzui Avatar answered Oct 05 '22 23:10

yurzui


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]         });       } 
like image 41
Rejayi CS Avatar answered Oct 06 '22 00:10

Rejayi CS