0

I have came across a situation where I need to implement logic for displaying nested angular mat table. I already have implemented logic for displaying nested (inner) table with data source of child element, But situation is tricky when there is third level child items which again can have nested elements. the example data can be seen below.

enter image description here

So far I have fetched data for parent data source and child data source and displayed using mat table but unable to define logic for generating child mat table at run time based on data receives from backend service (On particular row item click). Current implantation is as follow (this is for nested mat table for level 1):

  <ng-container matColumnDef="expandedDetail">
    <td mat-cell *matCellDef="let element" [attr.colspan]="displayedColumns.length">
      <div class="example-element-detail" *ngIf="element._mdbpartchilds?.length" [@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'">
        <div class="inner-table mat-elevation-z8" *ngIf="expandedElement">

          <table #innerTables mat-table #innerSort="matSort" [dataSource]="element._mdbpartchilds" matSort>
            <ng-container matColumnDef="{{innerColumn}}" *ngFor="let innerColumn of innerDisplayedColumns">
              <th mat-header-cell *matHeaderCellDef mat-sort-header> {{innerColumn}} </th>
              <td mat-cell *matCellDef="let element"> {{element[innerColumn]}} </td>
            </ng-container>
            <tr mat-header-row *matHeaderRowDef="innerDisplayedColumns"></tr>
            <tr mat-row *matRowDef="let row; columns: innerDisplayedColumns;"></tr>
          </table>
        </div>
      </div>
    </td>
  </ng-container>
  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let element; columns: displayedColumns;" [class.example-element-row]="element._mdbpartchilds?.length"
   [class.example-expanded-row]="expandedElement === element" (click)="toggleRow(element)">
  </tr>
  <tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>

But assuming if child element also contains some sub child rows how one should implement the logic ? The data for each level can be fetched on row or mat-cell click and data source will get bind to but I see a challenge here to update whole data source when new data arrives for nested table.

To summarize the items can have unpredictable no. of child items which should be displayed if child count is greater than 0. Any help would greatly appreciated.**

2 Answers2

0

Unfortunately no solution found with mat-table, but i found ngx-datatable an Angular component seems to be quite flexible in term of nesting and dynamic databinding. If this helps anyone who is looking for similar feature, here is GitHub repo : https://github.com/swimlane/ngx-datatable and here is npm command : npm i @swimlane/ngx-datatable --save

0

A little late but I came across same situation.I used ng-template recursively to solve this.

inside main table

        <ng-container matColumnDef="expandedDetail">
           <td mat-cell *matCellDef="let element" [attr.colspan]="displayedColumns.length">
                        <div class="element-detail"
                            [@detailExpand]="element === expandedElement ? 'expanded' : 'collapsed'">
                            <ng-container
                                *ngTemplateOutlet="innerTableTemplate; context: {$implicit: {data: element, index: 0,selectedData:null}}">
                            </ng-container>
                        </div>
           </td>
         </ng-container>

innerTableTemplate

<ng-template #innerTableTemplate let-templateData>
<ng-container *ngIf="templateData.data[returnInnerDataAccessKey(templateData.index)] !== undefined">
    <table mat-table [dataSource]="templateData.data[returnInnerDataAccessKey(templateData.index)]" matSort
        multiTemplateDataRows>

        <ng-container [matColumnDef]="'expand_table'+templateData.index">
            <th mat-header-cell *matHeaderCellDef> Expand </th>
            <td mat-cell *matCellDef="let element">
                <button mat-icon-button aria-label="expand row"
                    (click)="(templateData.selectedData = templateData.selectedData === element ? null : element); $event.stopPropagation()">
                    <mat-icon *ngIf="templateData.selectedData !== element">keyboard_arrow_down</mat-icon>
                    <mat-icon *ngIf="templateData.selectedData === element">keyboard_arrow_up</mat-icon>
                </button>
            </td>
        </ng-container>

        <ng-container *ngFor="let innerColumn of getInnerDisplayColumns(templateData.index);">
            <ng-container [matColumnDef]="innerColumn" *ngIf="innerColumn !== 'expand_table'+templateData.index">
                <th mat-header-cell *matHeaderCellDef mat-sort-header>
                    <span>{{innerColumn}}</span>
                </th>
                <td mat-cell *matCellDef="let element">
                    <div>{{element[innerColumn]}}</div>
                </td>
            </ng-container>
        </ng-container>

        <ng-container [matColumnDef]="'expandedDetail'">
            <td mat-cell *matCellDef="let element" [attr.colspan]="displayedColumns.length">
                <div class="element-detail"
                    [@detailExpand]="element === templateData.selectedData ? 'expanded' : 'collapsed'">
                    <ng-container
                        *ngTemplateOutlet="innerTableTemplate; context: {$implicit: {data: element, index: templateData.index+1,selectedData:null}}">
                    </ng-container>
                </div>
            </td>
        </ng-container>

        <tr mat-header-row *matHeaderRowDef="getInnerDisplayColumns(templateData.index)">
        </tr>
        <tr mat-row *matRowDef="let row; columns: getInnerDisplayColumns(templateData.index);">
        </tr>
        <tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="table-row">
        </tr>

    </table>
</ng-container>

functions used:

getInnerDisplayColumns()

getInnerDisplayColumns(currentLevel: number) {
  let columnsToBeDisplayed = [...this.nestedTableColumns][currentLevel][1];
  if (currentLevel < (this.maximumLevels - 1)) {
      columnsToBeDisplayed.unshift('expand_table' + currentLevel);
  }
 return columnsToBeDisplayed ?? [];
}   

returnInnerDataAccessKey()

  returnInnerDataAccessKey(currentLevel: number) {
    return [...this.nestedTableColumns][currentLevel][0];
   }

nestedTableColumns is a map which contains <key,displayedColumns>.