javascriptangularangular-material

How to Create a Nested Array from 2 Tables in Angular


I have 2 tables that I need to display in the following manner: Nested Table I'm populating 2 different arrays

storeTableData: any=[];
employeeTableData: any=[];

The storeTableData has the following fields: StoreID, Name, Address
The employeeTableData has the following fields: StoredID, Name, Role

I'm not sure if I have to turn my data into an array like this first (and if so, how?):

Store[] = [
{
  StoreID: "1",
  Store: "Staples",
  address: "123 Main Street, San Diego CA 12345",
  employees: [
    {
      StoreID: "1",
      Name: "John Doe",
      Role: "Manager"
    },
    {
      StoreID: "1",
      Name: "John Smith",
      Role: "Cashier"
    },
    {
      StoreID: "1",
      Name: "Jane Doe",
      Role: "Shipping"
    },
},
{
  StoreID: "2",
  Store: "Best Buy",
  address: "456 Main Street, San Diego CA 12345",
  employees: [
    {
      StoreID: "2",
      Name: "John Smith",
      Role: "Manager"
    },
    {
      StoreID: "2",
      Name: "Jane Doe",
      Role: "Cashier"
    },
    {
      StoreID: "2",
      Name: "John Doe",
      Role: "Shipping"
    },
},
]

Or is there a way I can just do it via a nested ngif in the html or is there some other way?


Solution

  • As mentioned in the comment, there is no point to split the store and employees data into separate array if you are constructing the nested table as the screenshot.

    Updated

    Well, if both stores and employees data come from separate arrays, you should join it together in your back-end API/database so you will need single API call. (Unfortunately you didn't cover on how you obtain these data from the back-end API or database, hence I will not cover this topic).

    If you are not able/granted to build/change the API for the mentioned joining data, you can look for joining both storeTableData and employeeTableData in the front-end:

    const storeWithEmployeesData: any[] = storeTableData.map((store) => ({
        ...store,
        employees: employeeTableData.filter((e) => store.StoreID == e.StoreID)
    }));
    

    Taking the example for my written answer on Expanding multiple rows in a nested mat table in Angular not working, you can construct the nested table in the column as well.

    Note that if you implement the sorting feature in the nested table, your employees array should be transformed to MatTableDataSource<T> type and assign the MatSort instance accordingly.

    <table
      mat-table
      #outerSort="matSort"
      [dataSource]="dataSource"
      multiTemplateDataRows
      class="mat-elevation-z8"
      matSort
    >
      <ng-container matColumnDef="Store">
        <th mat-header-cell *matHeaderCellDef mat-sort-header=>Store</th>
        <td mat-cell *matCellDef="let element">{{element.Store}}</td>
      </ng-container>
    
      <ng-container matColumnDef="address">
        <th mat-header-cell *matHeaderCellDef mat-sort-header>Address</th>
        <td mat-cell *matCellDef="let element">{{element.address}}</td>
      </ng-container>
    
      <ng-container matColumnDef="employees">
        <th mat-header-cell *matHeaderCellDef>Employees</th>
        <td mat-cell *matCellDef="let element">
          <table
            #innerTables
            mat-table
            #innerSort="matSort"
            [dataSource]="element.employees"
            matSort
          >
            <ng-container
              *ngFor="let innerColumn of innerDisplayedColumns"
              [matColumnDef]="innerColumn"
            >
              <th mat-header-cell *matHeaderCellDef [mat-sort-header]="innerColumn">
                {{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>
        </td>
      </ng-container>
    
      <tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
      <tr mat-row *matRowDef="let element; columns: columnsToDisplay;"></tr>
    </table>
    
    import {
      QueryList,
      ViewChild,
      ViewChildren,
    } from '@angular/core';
    import {
      MatTable,
      MatTableDataSource,
      MatTableModule,
    } from '@angular/material/table';
    import { MatSort, MatSortModule } from '@angular/material/sort';
    
    @ViewChild('outerSort', { static: true }) sort: MatSort;
      dataSource: MatTableDataSource<any>;
    @ViewChildren('innerSort') innerSort: QueryList<MatSort>;
    @ViewChildren('innerTables') innerTables: QueryList<MatTable<any>>;
    columnsToDisplay = ['Store', 'address', 'employees'];
    innerDisplayedColumns = ['Name', 'Role'];
    
    ngOnInit() {
      let storesData: any[] = this.stores.map((store) => ({
        ...store,
        employees: new MatTableDataSource(store.employees || []),
      }));
    
      this.dataSource = new MatTableDataSource(storesData);
      this.dataSource.sort = this.sort;
    }
    
    ngAfterViewInit() {
      this.innerTables.forEach((table, index) => {
        const dataSource = table.dataSource as MatTableDataSource<any>;
        dataSource.sort = this.innerSort.toArray()[index];
      });
    }
    

    Demo @ StackBlitz