Danny Hoeve
Danny Hoeve

Reputation: 722

Mat-table multiple row within a multiple row

What I want is drawn out in the following image. I use Angular Material (7.x) and use the Mat-Table implementation as described here: https://material.angular.io/components/table/overview

enter image description here

The above scenario is for the business to show multiple Sales Invoices.

I used Mat Table of Angular already for using multipe rows for one row, and this works. But now I also have to make the first row (Type A) have multiple rows within the multiple row table.

I have some ideas on how to be able to get this working, but I don't want to start and know beforehand if the solution is going to work or not.

I started iterating over the objects in the salesInvoices:

  <tr mat-header-row *matHeaderRowDef="displayedPurchaseInvoiceColumns"></tr>
  <ng-container *ngFor="let item of invoices.salesInvoice">
    <tr mat-row class="table-first-row" *matRowDef="let row; columns: displayedSalesInvoiceColumns"></tr>
  </ng-container>
  <tr mat-row class="table-second-row" *matRowDef="let row; columns: displayedPurchaseInvoiceColumns"></tr>

This doesn't show anything useful. I think it doesn't even do anything as I only see the purchase Invoices displayed, not the sales invoices.

I was thinking about using a mat-table for the first row? I have no idea how I am able to do so though, I am also not sure if this will work.

I really want to get this working WITH Mat-Table but I am afraid I won't be able to get it working with the library of Material and have to make something custom.

Any help is welcome! If I need to provide more information, please tell me.

Upvotes: 15

Views: 9310

Answers (3)

Wilt
Wilt

Reputation: 44346

Combine multiTemplateDataRows with when predicate on MatRowDef.

You are looking for multiTemplateDataRows in combination with a when predicate.

The input multiTemplateDataRows is a boolean set on the MatTable, see the documentation here: https://material.angular.io/components/table/api#MatTable

Whether to allow multiple rows per data object by evaluating which rows evaluate their 'when' predicate to true. If multiTemplateDataRows is false, which is the default value, then each data object will render the first row that evaluates its when predicate to true, in the order defined in the table, or otherwise the default row which does not have a when predicate.

The when predicate on the MatRowDef decides which template should be used for which item. See the documentation here: https://material.angular.io/components/table/api#MatRowDef

Function that should return true if this row template should be used for the provided index and row data. If left undefined, this row will be considered the default row template to use when no other when functions return true for the data. For every row, there must be at least one when function that passes or an undefined to default.

Note: If the when predicate on several row definitions returns true for the same item, all of those rows will be rendered. If you have multiple row definitions without a when predicate, all of those will render.


In other words, you should set the multiTemplateDataRows to true, define several row definitions and the when predicate of several of those row definitions should return true for the same item (or you don't add a when predicate at all) -> now you rendered multiple rows for your item the "material-table" way...

<table mat-table [dataSource]="dataSource" multiTemplateDataRows>
   
  <!-- Your column definitions -->
  <ng-container matColumnDef="name">
    <th mat-header-cell *matHeaderCellDef>Name</th>
    <td mat-cell *matCellDef="let item">{{item.name}}</td>
  </ng-container>      

  <!-- Your row definitions -->
  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns; when: yourFirstPredicate"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns; when: yourSecondPredicate"></tr>

  <!-- Add as many as you want... -->

</table>

Your predicates should something like this:

public yourFirstPredicate(index: number, row: any): boolean {
  // some code returning a boolean...
}

public yourSecondPredicate(index: number, row: any): boolean {
  // some code returning a boolean...
}

They take the index as a first argument, and the row as a second argument.


Here a working example in StackBlitz

Upvotes: 4

user3408531
user3408531

Reputation:

Use a model like:

export class Model{
parent?: Parent;
children?: Child[];
}

Ts:

public displayedColumns = ['parents', 'children'];


models: Model[] = [];
//Fill your models from DB and then
this.dataSource = new MatTableDataSource<any>(this.models);

Template:

        <mat-table #table [dataSource]="dataSource" class="mat-elevation-z8">
                <ng-container matColumnDef="parents">
                    <mat-header-cell *matHeaderCellDef> Parent</mat-header-cell>
                    <mat-cell *matCellDef="let element">
                        <ng-container >
                            {{element.parent.name}}
                            <br />
                        </ng-container>
                    </mat-cell>
                </ng-container>
                <ng-container matColumnDef="children">
                    <mat-header-cell *matHeaderCellDef> Children </mat-header-cell>
                    <mat-cell *matCellDef="let element">
                        <ng-container *ngFor="let chl of element.children">
                                {{chl.name}}
                                <br /> 
                        </ng-container>
                    </mat-cell>
                </ng-container>
                <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
                <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
        </mat-table>

Stackblitz

Upvotes: 0

Drashti Dobariya
Drashti Dobariya

Reputation: 3006

You can create a two-level nested Mat-table in Angular in which it can expand multiple rows at a time. On each row click from the parent grid it calls an API and loads the data for the inner grid.

Component Code:

ngOnInit() {
    this.tableService.getData().subscribe(res => {
      if (res.length == 0) {
        this.panelDataSource = new MatTableDataSource();
      } else {
        console.log(res);
        this.panelDataSource = new MatTableDataSource(res);
      }
    });
  }

  getDetails(element: any) {
    
    this.tableService.getInnerData(element.Id).subscribe(res => {
      if (res.length == 0) {
        element['innerDatasource'] = new MatTableDataSource();
      } else {
        
        element['innerDatasource'] = new MatTableDataSource(res);
      }
    });
  }
}

HTML Code:

<table mat-table [dataSource]="panelDataSource" multiTemplateDataRows matSort>
    <ng-container [matColumnDef]="column" *ngFor="let column of columnsToDisplay">
        <th mat-header-cell *matHeaderCellDef> {{column}} </th>
        <td mat-cell (click)="getDetails(element)" *matCellDef="let element"> {{element[column]}} </td>
    </ng-container>

    <ng-container matColumnDef="expandedDetail">
        <td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplay.length">
            <div class="example-element-detail" [@detailExpand]="element?.expanded" *ngIf="element?.expanded">
                <div style="width: 100%;">

                    <table mat-table [dataSource]="element.innerDatasource" multiTemplateDataRows 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 address"> {{address[innerColumn]}} </td>
                        </ng-container>

                        <tr mat-header-row *matHeaderRowDef="innerDisplayedColumns"></tr>
                        <tr mat-row *matRowDef="let address; columns: innerDisplayedColumns;"
                            [class.example-element-row]="address.comments?.length" [class.example-expanded-row]="address?.expanded"
                            (click)="address.expanded = !address?.expanded">
                        </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?.length" [class.example-expanded-row]="element?.expanded"
        (click)="element.expanded = !element?.expanded">
    </tr>
    <tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
</table>

Working Stackblitz

Upvotes: 0

Related Questions