How can I have nested mat-table sortables?

Viewed 2024

Ciao,

I'm playing with Angular 8 and I need one main table with expandable rows and for each row I need see the the details.

My actual problem is that the sorting works only on father table and not on the children ones.

This is my HTML code:

<table mat-table [dataSource]="dataSource" multiTemplateDataRows matSort #sorter1="MatSort">
    <ng-container matColumnDef="id">
        <th mat-header-cell *matHeaderCellDef> ID </th>
        <td mat-cell *matCellDef="let element"> {{ element.id }} </td>
    </ng-container>

    <ng-container matColumnDef="expandedDetail">
        <td mat-cell *matCellDef="let element" [attr.colspan]="2">
            <div>
                <table mat-table [dataSource]="dataSource2" matSort #innerSorts="MatSort">
                    <ng-container matColumnDef="id">
                        <th mat-header-cell *matHeaderCellDef> ID </th>
                        <td mat-cell *matCellDef="let element"> {{ element.id }} </td>
                    </ng-container>

                    <tr mat-header-row *matHeaderRowDef="['id']"></tr>
                    <tr mat-row *matRowDef="let element; columns: ['id'];"></tr>
                </table>
            </div>
        </td>
    </ng-container>

    <tr mat-header-row *matHeaderRowDef="['id']"></tr>
    <tr mat-row *matRowDef="let element; columns: ['id'];"
        class="example-element-row"
        [class.example-expanded-row]="expandedElement === element"
        (click)="clickRow(element)">
    </tr>
    <tr mat-row *matRowDef="let row; columns: ['expandedDetail']"></tr>
</table>

This is my TypeScript code:

imports ...

@Component({
selector: 'my-nested-tables',
styleUrls: ['my-nested-tables.css'],
templateUrl: 'my-nested-tables.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 MyNestedTablesComponent {
    @Input() element: Array<Element>;
    @Input() element2: Array<Element>;

    dataSource1: MatTableDataSource<Element>(this.element);
    dataSource2: MatTableDataSource<Element>(this.element2);
    expandedElement: Element;

    @ViewChild("sorter1", {static:true}) sorter1: MatSort;
    @ViewChildren("innerTables") tables: QueryList<MatTable<Element>>
    @ViewChildren("innerSorts") innerSorts: QueryList<MatSort>;

    constructor(private cd: ChangeDetectorRef) { }

    ngOnInit() {
        this.dataSource.sort = sorter1;

    }

    ngOnChanges(change: SimpleChanges) {
        this.dataSource1.data = this.element;
        this.dataSource2.data = this.element2;
    }

    clickRow(element: Element) : void {
        this.expandedElement = this.expandedElement === element ? null : element;
        this.onLoadMyNestedElement.emit(this.expandedElement.id); // This is a event that update the element2 property (HTTP request).
        this.cd.detectChanges();
        this.tables.forEach((table, index) => (table.dataSource as MatTableDataSource<Element>).sort = this.innerSorts.toArray()[index]);
    }
}

export class Element {
    public id: number;
}

Can you help me?

Grazie mille!

1 Answers

Material tables can be complex and frustrating to wire up, especially with sorting and filtering.

What will help simplify things, is breaking out the inner tables into a separate component and added the reference into the parent table.

This also has the benefit of being separately testable. Try the following setup (I can't verify it works, but it will be a good start)

main-table.component.ts

<inner-table [data]="element.data"></inner-table>

inner-table.component.ts

import { MatTableDataSource, MatSort } from '@angular/material';
import { Component, Input, ViewChild } from '@angular/core';

@Component({
  selector: "inner-table",
  template: `
    <table mat-table [dataSource]="dataSource" matSort>
      <ng-container matColumnDef="id">
        <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>
        <td mat-cell *matCellDef="let element">{{ element.id }}</td>
      </ng-container>

      <tr mat-header-row *matHeaderRowDef="['id']"></tr>
      <tr mat-row *matRowDef="let element; columns: ['id']"></tr>
    </table>
  `
})
export class InnerTableComponent {
  @Input()
  set data(newData: []) {
    this.dataSource.data = newData;
  }

  dataSource: MatTableDataSource<Element>;

  @ViewChild(MatSort, {static: false}) sort: MatSort;

  ngOnInit() {
    this.dataSource = new MatTableDataSource();
    this.dataSource.sort = this.sort;
  }
}

I've also made my own angular material table called ngx-auto-table to help with data tables, check it out it might help you.

Related