4

I'm using Angular 7 and using mat-table. https://material.angular.io/components/table/overview

However, all examples I can find use a simple array to create the mat-table from. My array (see below) has an array in an array. I need to iterate over the Results array and then the "line_items" array. But can't find a single example of how this is done?

Component.ts

<mat-table [dataSource]="dataSource" matSort (matSortChange)="sortChanged($event)">

    <ng-container matColumnDef="line_items">
      <mat-header-cell *matHeaderCellDef mat-sort-header>line_items</mat-header-cell>
      <mat-cell *matCellDef="let element">{{element.line_items}}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="name">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
      <mat-cell *matCellDef="let element">{{element.name}}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="number">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Number</mat-header-cell>
      <mat-cell *matCellDef="let element">{{element.number}}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="category">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Category</mat-header-cell>
      <mat-cell *matCellDef="let element">{{element.category}}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="original_budget">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Original Budget</mat-header-cell>
      <mat-cell *matCellDef="let element">{{element.original_budget}}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="approved_cos">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Approved Cos</mat-header-cell>
      <mat-cell *matCellDef="let element">{{element.approved_cos}}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="revised_budget">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Revised Budget</mat-header-cell>
      <mat-cell *matCellDef="let element">{{element.revised_budget}}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="pending_budget_changes">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Pending Budget Changes</mat-header-cell>
      <mat-cell *matCellDef="let element">{{element.pending_budget_changes}}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="projected_budget">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Projected Budget</mat-header-cell>
      <mat-cell *matCellDef="let element">{{element.projected_budget}}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="loading">
      <mat-footer-cell *matFooterCellDef colspan="6">
        Loading data...
      </mat-footer-cell>
    </ng-container>

    <ng-container matColumnDef="noData">
      <mat-footer-cell *matFooterCellDef colspan="6">
        No data.
      </mat-footer-cell>
    </ng-container>

    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
    <mat-row *matRowDef="let row; columns: displayedColumns;" (click)="selectRow(row, $event)" (dblclick)="onEdit(row, $event)"></mat-row>

    <mat-footer-row *matFooterRowDef="['loading']" [ngClass]="{'hide':dataSource!=null}"></mat-footer-row>
    <mat-footer-row *matFooterRowDef="['noData']" [ngClass]="{'hide':!(dataSource!=null && dataSource.data.length==0)}"></mat-footer-row>
</mat-table>

Array

{
  "results": [
    {
      "name": "General Requirements",
      "id": "1",
      "line_items": [
        {
          "name": "Project Manager",
          "number": "442",
          "category": "L",
          "original_budget": 30000,
          "approved_cos": 0,
          "revised_budget": 30000,
          "pending_budget_changes": 0,
          "projected_budget": 30000,
          "committed_costs": 0,
          "direct_costs": 0,
          "pending_costs_changes": 0,
          "projected_costs": 0,
          "forecast_to_complete": 30000,
          "estimated_cost_at_completion": 30000,
          "projected_over_under": 0
        },
        {
          "name": "Project Engineer",
          "number": "564",
          "category": "L",
          "original_budget": 17000,
          "approved_cos": 0,
          "revised_budget": 17000,
          "pending_budget_changes": 0,
          "projected_budget": 17000,
          "committed_costs": 0,
          "direct_costs": 0,
          "pending_costs_changes": 0,
          "projected_costs": 0,
          "forecast_to_complete": 17000,
          "estimated_cost_at_completion": 17000,
          "projected_over_under": 0
        },
        {
          "name": "Superintendent",
          "number": "766",
          "category": "L",
          "original_budget": 55000,
          "approved_cos": 0,
          "revised_budget": 55000,
          "pending_budget_changes": 0,
          "projected_budget": 55000,
          "committed_costs": 0,
          "direct_costs": 0,
          "pending_costs_changes": 0,
          "projected_costs": 0,
          "forecast_to_complete": 55000,
          "estimated_cost_at_completion": 55000,
          "projected_over_under": 0
        },
        {
          "name": "Project Coordinator",
          "number": "876",
          "category": "L",
          "original_budget": 5000,
          "approved_cos": 0,
          "revised_budget": 5000,
          "pending_budget_changes": 0,
          "projected_budget": 5000,
          "committed_costs": 0,
          "direct_costs": 0,
          "pending_costs_changes": 0,
          "projected_costs": 0,
          "forecast_to_complete": 5000,
          "estimated_cost_at_completion": 5000,
          "projected_over_under": 0
        }
      ]
    },
    {
      "name": "Doors And Windows",
      "id": "2",
      "line_items": [
        {
          "name": "Doors",
          "number": "600",
          "category": "O",
          "original_budget": 2000,
          "approved_cos": 0,
          "revised_budget": 2000,
          "pending_budget_changes": 0,
          "projected_budget": 2000,
          "committed_costs": 0,
          "direct_costs": 0,
          "pending_costs_changes": 0,
          "projected_costs": 0,
          "forecast_to_complete": 2000,
          "estimated_cost_at_completion": 2000,
          "projected_over_under": 0
        },
        {
          "name": "Doors",
          "number": "600",
          "category": "S",
          "original_budget": 67000,
          "approved_cos": 0,
          "revised_budget": 67000,
          "pending_budget_changes": 0,
          "projected_budget": 67000,
          "committed_costs": 0,
          "direct_costs": 0,
          "pending_costs_changes": 0,
          "projected_costs": 0,
          "forecast_to_complete": 67000,
          "estimated_cost_at_completion": 67000,
          "projected_over_under": 0
        }
      ]
    },
    {
      "name": "Finishes",
      "id": "3",
      "line_items": [
        {
          "name": "Kitchen Sink",
          "number": "800",
          "category": "O",
          "original_budget": 5000,
          "approved_cos": 0,
          "revised_budget": 5000,
          "pending_budget_changes": 0,
          "projected_budget": 5000,
          "committed_costs": 0,
          "direct_costs": 0,
          "pending_costs_changes": 0,
          "projected_costs": 0,
          "forecast_to_complete": 5000,
          "estimated_cost_at_completion": 5000,
          "projected_over_under": 0
        }
      ]
    }


  ]
}

UPDATE

I tried the below, hoping it would work, but nope...

<ng-container *ngFor="let budget of budgets">

    <ng-container *ngFor="let item of budget.line_items">

      {{item | json}}

  <mat-table [dataSource]="item" matSort (matSortChange)="sortChanged($event)">

enter image description here

6
  • I've removed references to JSON, since what you have are arrays; it doesn't matter where your data came from. Commented Apr 23, 2019 at 15:44
  • What do you want to do with the nested objects within the line_items objects? Just have a subtable? Commented Apr 23, 2019 at 15:48
  • A subtable would be fine. Really all I need from the Results array is the 'name' property. So the line_items is the main table(s) I need. Perhaps with the 'Name' property as a row... Commented Apr 23, 2019 at 15:51
  • I haven't used mat-tables, but it seems that this question is asking the same thing as you, and the first answer seems to suggest it works by simply iterating thru the nested data. So I guess where you have {{element.line_items}}, replace that with a nested *ngFor and iterate thru line_items. Commented Apr 23, 2019 at 15:57
  • That won't really give me what I need, because I really want line_items to be my table (headers) not just a column in the table... Perhaps, I should just flaten the array to a single array, then make that the dataSource? Just doesn't seen very graceful. But nobody has an example of what I want... Commented Apr 23, 2019 at 16:20

2 Answers 2

1

Based on the information provided I assume you are trying to have Mat-Table and a Nested Mat-Table for each of the rows in the parent table. This can be archived below. I have created a simple stackblitz sample with small data set similar to yours. In provided sample data set by myself addresses refer to your examples line_items attribute.

Please find the working sample stackblitz here.

Please find the code below :

component.ts :

import { Component, ViewChild, ViewChildren, QueryList, ChangeDetectorRef } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource, MatTable } from '@angular/material/table';

/**
 * @title Table with expandable rows
 */
@Component({
  selector: 'table-expandable-rows-example',
  styleUrls: ['table-expandable-rows-example.css'],
  templateUrl: 'table-expandable-rows-example.html',
  animations: [
    trigger('detailExpand', [
      state('collapsed', style({ height: '0px', minHeight: '0' })),
      state('expanded', style({ height: '*' })),
      transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
    ]),
  ],
})
export class TableExpandableRowsExample {

  @ViewChild('outerSort', { static: true }) sort: MatSort;
  @ViewChildren('innerSort') innerSort: QueryList<MatSort>;
  @ViewChildren('innerTables') innerTables: QueryList<MatTable<Address>>;

  dataSource: MatTableDataSource<User>;
  usersData: User[] = [];
  columnsToDisplay = ['name', 'email', 'phone'];
  innerDisplayedColumns = ['street', 'zipCode', 'city'];
  expandedElement: User | null;

  constructor(
    private cd: ChangeDetectorRef
  ) { }

  ngOnInit() {
    USERS.forEach(user => {
      if (user.addresses && Array.isArray(user.addresses) && user.addresses.length) {
        this.usersData = [...this.usersData, {...user, addresses: new MatTableDataSource(user.addresses)}];
      } else {
        this.usersData = [...this.usersData, user];
      }
    });
    this.dataSource = new MatTableDataSource(this.usersData);
    this.dataSource.sort = this.sort;
  }

  toggleRow(element: User) {
    element.addresses && (element.addresses as MatTableDataSource<Address>).data.length ? (this.expandedElement = this.expandedElement === element ? null : element) : null;
    this.cd.detectChanges();
    this.innerTables.forEach((table, index) => (table.dataSource as MatTableDataSource<Address>).sort = this.innerSort.toArray()[index]);
  }

  applyFilter(filterValue: string) {
    this.innerTables.forEach((table, index) => (table.dataSource as MatTableDataSource<Address>).filter = filterValue.trim().toLowerCase());
  }
}

export interface User {
  name: string;
  email: string;
  phone: string;
  addresses?: Address[] | MatTableDataSource<Address>;
}

export interface Address {
  street: string;
  zipCode: string;
  city: string;
}

export interface UserDataSource {
  name: string;
  email: string;
  phone: string;
  addresses?: MatTableDataSource<Address>;
}

const USERS: User[] = [
  {
    name: "Mason",
    email: "[email protected]",
    phone: "9864785214",
    addresses: [
      {
        street: "Street 1",
        zipCode: "78542",
        city: "Kansas"
      },
      {
        street: "Street 2",
        zipCode: "78554",
        city: "Texas"
      }
    ]
  },
  {
    name: "Eugene",
    email: "[email protected]",
    phone: "8786541234",
  },
  {
    name: "Jason",
    email: "[email protected]",
    phone: "7856452187",
    addresses: [
      {
        street: "Street 5",
        zipCode: "23547",
        city: "Utah"
      },
      {
        street: "Street 5",
        zipCode: "23547",
        city: "Ohio"
      }
    ]
  }
];

component.html :

<table mat-table #outerSort="matSort" [dataSource]="dataSource" multiTemplateDataRows class="mat-elevation-z8" matSort>
    <ng-container matColumnDef="{{column}}" *ngFor="let column of columnsToDisplay">
        <th mat-header-cell *matHeaderCellDef mat-sort-header> {{column}} </th>
        <td mat-cell *matCellDef="let element"> {{element[column]}} </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" [attr.colspan]="columnsToDisplay.length">
            <div class="example-element-detail" *ngIf="element.addresses?.data.length" [@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'">
                <div class="inner-table mat-elevation-z8" *ngIf="expandedElement">
          <mat-form-field>
            <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
          </mat-form-field>
          <table #innerTables mat-table #innerSort="matSort" [dataSource]="element.addresses" 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="columnsToDisplay"></tr>
    <tr mat-row *matRowDef="let element; columns: columnsToDisplay;" [class.example-element-row]="element.addresses?.data.length"
     [class.example-expanded-row]="expandedElement === element" (click)="toggleRow(element)">
    </tr>
    <tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
</table>
Sign up to request clarification or add additional context in comments.

Comments

1

Another approach can be use a single table. You create an array like

dataExpanded=this.data.results.reduce((a:any,b:any)=>{
     return a.concat({name:b.name,id:b.id,number: "",category: "O",original_budget: 0,approved_cos: 0,revised_budget:0,pending_budget_changes: 0,projected_budget: 0,committed_costs: 0,direct_costs: 0,pending_costs_changes: 0,projected_costs: 0,forecast_to_complete: 0,estimated_cost_at_completion: 0,projected_over_under:0
    },...b.line_items.map((x:any)=>({id:0,...x})))
  },[])

And a table like

<table mat-table [dataSource]="dataExpanded" class="mat-elevation-z8">
  <ng-container *ngFor="let column of displayedColumns;let i=index" [matColumnDef]="column">
    <th mat-header-cell *matHeaderCellDef>{{header[i]}}</th>
    <ng-container *matCellDef="let element">
    <td class="header" mat-cell [attr.colspan]="15" *ngIf="element.id && column=='name'">
      {{element.name}}
    </td>
    <ng-container *ngIf="!element.id">
      <td *ngIf="column!='name'" mat-cell > {{element[column]}} </td>
      <td *ngIf="column=='name'" mat-cell > {{element.number}} - {{element.name}} </td>
    </ng-container>
    </ng-container>
  </ng-container>
  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

See stackblitz

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.