IvanS95
IvanS95

Reputation: 5732

How to build a dynamic form in a mat-table's expandable row?

I'm trying to build a table that allows me to perform multiple tasks on it; among those, I'm trying to implement the ability to edit the information of the record right there.

My idea is to create the mat-table as usual, with an additional expandable row to display a small form with the fields I can edit and a button to save the changes and update the table. However I'm not sure on how to actually build this, since I'm not sure on how to build a reactive form for each row in the table; I wouldn't be able to declare it in the components TypeScript file like:

rowForm = new FormGroup({
            control1: new FormControl() 
         })

Because I need it for each record in the table.

 <table mat-table matSort [dataSource]="dataSource" class="table table-borderless admin-users-table" multiTemplateDataRows>
     <ng-container matColumnDef="name">
         <th mat-header-cell *matHeaderCellDef mat-sort-header> NAME </th>
         <td mat-cell *matCellDef="let user">
             <a href="javascript:" [routerLink]="['/setup/users', user._id]">
                 {{ user.name }}
             </a>
         </td>
     </ng-container>

     <ng-container matColumnDef="email">
         <th mat-header-cell *matHeaderCellDef mat-sort-header> EMAIL </th>
         <td mat-cell *matCellDef="let user">
             {{ user.email }}
         </td>
     </ng-container>

     <ng-container matColumnDef="id">
         <th mat-header-cell *matHeaderCellDef> USER ID </th>
         <td mat-cell *matCellDef="let user">
             {{ user._id }}
         </td>
     </ng-container>

     <ng-container matColumnDef="org">
         <th mat-header-cell *matHeaderCellDef mat-sort-header> ORGANIZATION </th>
         <td mat-cell *matCellDef="let user">
             {{ user.organization.name }}
         </td>
     </ng-container>
                
     <!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
     <ng-container matColumnDef="expandedDetail">
         <td mat-cell *matCellDef="let element" class="py-0" [attr.colspan]="columnsToDisplay.length">
             <div class="user-element-detail"
                        [@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'">
                  <div class="user-element-diagram">
                      <div class="user-element-position"> {{element.position}} </div>
                      <div class="user-element-symbol"> {{element.symbol}} </div>
                      <div class="user-element-name"> {{element.name}} </div>
                      <div class="user-element-weight"> {{element.weight}} </div>
                  </div>
                  <div class="user-element-description">
                        {{element.description}}
                     <span class="user-element-description-attribution"> -- Wikipedia </span>
                  </div>
             </div>
         </td>
      </ng-container>


      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr mat-row *matRowDef="let element; columns: displayedColumns;"
                    class="user-element-row"
                    [class.user-expanded-row]="expandedElement === element"
                    (click)="expandedElement = expandedElement === element ? null : element">
      </tr>
      <tr mat-row *matRowDef="let row; columns: ['expandedDetail']"></tr>
</table>

Any ideas on how should I approach this or if there's a more optimal way to do this kind of feature?

Upvotes: 2

Views: 3196

Answers (1)

Eliseo
Eliseo

Reputation: 57929

the idea is that your "dataSource" has a form. e.g., you create a function like

createForm(data:any)
  {
    return new FormGroup({
      name:new FormControl(data.name),
      weight:new FormControl(data.weight),
      symbol:new FormControl(data.symbol),
      position:new FormControl(data.position),
    })
  }

Your data is then

dataSource = ELEMENT_DATA.map(x=>({...x,form:this.createForm(x)}));

In your expandable row you add the form and two buttons "ok" "cancel"

    <form [formGroup]="element.form">
      <input formControlName="name"/>
      <button (click)="element.name=element.form.value.name;
                       expandedElement=null">ok</button>
      <button (click)="element.form.patchValue(element);
                       expandedElement=null">cancel</button>
    </form>

You can see in stackblitz

NOTE: Another option to manage is "edit in place" as in this SO

Update about the note, we can transform the ELEMENT_DATA in a FormArray

dataSource = new FormArray(ELEMENT_DATA.map(x=>this.createForm(x)));

Then we nned an auxiliar function taht return the formGroup at index

  getGroup(index:number)
  {
    return this.dataSource.at(index) as FormGroup
  }

The last if use, e.g. getGroup(i).get('position') to get the control and use as dataSource dataSource.controls

<table mat-table
   [dataSource]="dataSource.controls" multiTemplateDataRows
   class="mat-elevation-z8">
<ng-container matColumnDef="{{column}}" *ngFor="let column of columnsToDisplay">
    <th mat-header-cell *matHeaderCellDef> {{column}} </th>
    <1--see that we use let i=datIndex becaouse is a multiTemplateDataRows-->
    <td mat-cell *matCellDef="let element;let i=dataIndex"> {{getGroup(i).get(column).value}} </td>
  </ng-container>
....
<table>

And in expandable row:

<form [formGroup]="getGroup(i)">
  <input formControlName="name"/>
</form>

Well, the only is that we has no buttons "ok" and "cancel", when chang ethe input you see how the data change automatically

see a new stackblitz

Upvotes: 3

Related Questions