Reputation: 2587
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)">
Upvotes: 4
Views: 5173
Reputation: 57939
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
Upvotes: 1
Reputation: 6813
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>
Upvotes: 1