Expandable table rows in angular 4 with angular material

AngularAngular Material

Angular Problem Overview


How would you make rows expandable in angular material tables? One requirement is that I need to be using the angular material table. I would also prefer to use the material accordion to the information provided here.

I want to click on row and show different information for each column. Im looking for something like below. If you click on row 1, rows 2 and 3 appear with different data.

enter image description here

Angular Solutions


Solution 1 - Angular

As mentioned here by Andrew Seguin this is already feasible out of the box: using the when predicate.

See this example: https://stackblitz.com/edit/angular-material-expandable-table-rows (thx to Lakston)

demo

Inside of the mat-table tag you have to use the mat-row component with a matRipple directive. When you click on a row the row element will be assigned to the expandedElement variable:

<mat-row *matRowDef="let row; columns: displayedColumns;"
        matRipple 
        class="element-row" 
        [class.expanded]="expandedElement == row"
        (click)="expandedElement = row">
</mat-row>

But now we have to add our expanded row, that is hidden by default and will be shown if the user clicks on the row above:

<mat-row *matRowDef="let row; columns: ['expandedDetail']; when: isExpansionDetailRow"
        [@detailExpand]="row.element == expandedElement ? 'expanded' : 'collapsed'"
        style="overflow: hidden"> 
</mat-row>

Important is here the already mentioned when predicate. This calls a isExpansionDetailRow function that is defined in the component itself and checks if the row has a detailRow property:

isExpansionDetailRow = (row: any) => row.hasOwnProperty('detailRow');

Since RC0 the first param is the index:

isExpansionDetailRow = (i: number, row: any) => row.hasOwnProperty('detailRow');

If you want to have an expanded view for every row, you have to add an "ExpansionDetailRow" identified by the detailRow property for every row like this:

connect(): Observable<Element[]> {
    const rows = [];
    data.forEach(element => rows.push(element, { detailRow: true, element }));
    return Observable.of(rows);
}

If you would log the rows variable to the console output, it will look like this:

console output

EDIT: COMPLETE EXAMPLE USING DIRECTIVE

Mat Table expandable rows (sorting, pagination and filtering)

Solution 2 - Angular

It is not possible out of the box, but you can solve it with a little custom code. Take a look at this discussion and this solution (not from me, but the basis for this answer).

In short: Use the material table and add a click-method to the rows:

<md-row *mdRowDef="let row; columns: displayedColumns; let index=index" (click)="expandRow(index, row)" #myRow></md-row>

Add a component for the expanded area. The row_detail.html contains the html which is in the expanded area.

@Component({
  selector: 'app-inline-message',
  templateUrl: 'row_detail.html',
  styles: [`
    :host {
      display: block;
      padding: 24px;
      background: rgba(0,0,0,0.03);
    }
  `]
})
export class InlineMessageComponent {
  @Input() content1: string;
  @Input() content2: string;
}

In your component where the table lives you need the method to expand the row. First, add this to your component...

expandedRow: number;
@ViewChildren('myRow', { read: ViewContainerRef }) containers;

... and then add the method:

/**
   * Shows the detail view of the row
   * @param {number} index
   */
expandRow(index: number, row: DataFromRowFormat) {

    if (this.expandedRow != null) {
      // clear old message
      this.containers.toArray()[this.expandedRow].clear();
    }

    if (this.expandedRow === index) {
      this.expandedRow = null;
    } else {
      const container = this.containers.toArray()[index];
      const factory: ComponentFactory<InlineMessageComponent> = this.resolver.resolveComponentFactory(InlineMessageComponent);
      const messageComponent = container.createComponent(factory);

      messageComponent.instance.content1= "some text";
      messageComponent.instance.content2 = "some more text";
    }
}

Solution 3 - Angular

When CDK was the only way to get something close to a Material Table, using md-row's in a regular table was a viable alternative, but since @angular/material 2.0.0-beta.12 ditched CDK tables and now have their own Data Tables that might fit your needs. See documentation below:

https://material.angular.io/components/table/overview

Solution 4 - Angular

Here is how you can do it with Angular Material. This example includes pagination and one example with mat-sort-header on the 'name' column. I had to override mat paginator and do a custom sort to make sure the expandable row was still by its parent when it was sorted. This example also allows you to open multiple rows at a time and to close all rows

https://stackblitz.com/edit/angular-material2-issue-zs6rfz

Solution 5 - Angular

This answer is for only using Angular, we can do the Table row expand and collapse option.

I have given StackBlitz

In the TS file, we have created a variable to store table data.

data = [
    {
      id: 1,
      name: 'Abc',
      email: '[email protected]',
      isExpand: false,
      address: [
        {
          add1: 'Delhi',
          add2: 'Bangalore',
        }
      ]
    },
    {
      id: 2,
      name: 'Xyz',
      email: '[email protected]',
      isExpand: false,
      address: [
        {
          add1: 'Mumbai',
          add2: 'Pune',
        }
      ]
    },
    {
      id: 3,
      name: 'ijk',
      email: '[email protected]',
      isExpand: false,
      address: [
        {
          add1: 'Chennai',
          add2: 'Bangalore',
        }
      ]
    },
    {
      id: 4,
      name: 'def',
      email: '[email protected]',
      isExpand: false,
      address: [
        {
          add1: 'Kolkata',
          add2: 'Hyderabad',
        }
      ]
    }
  ]

In the HTML file, we have a table.

<table>
  <thead>
    <tr>
      <th></th>
      <th>SL</th>
      <th>Name</th>
      <th>Email</th>
    </tr>
  </thead>
  <tbody>
    <ng-container *ngFor="let row of data">
      <tr>
        <td (click)="row.isExpand = !row.isExpand">
          <span *ngIf="!row.isExpand">+</span>
          <span *ngIf="row.isExpand">-</span>
        </td>
        <td>{{ row.id }}</td>
        <td>{{ row.name }}</td>
        <td>{{ row.email }}</td>
      </tr>
      <tr *ngIf="row.isExpand">
        <td colspan="4">
          <table>
            <thead>
              <th>Address1</th>
              <th>Address2</th>
            </thead>
            <tbody>
              <tr *ngFor="let row2 of row.address">
                <td>{{ row2.add1 }}</td>
                <td>{{ row2.add2 }}</td>
              </tr>
            </tbody>
          </table>
        </td>
      </tr>
    </ng-container>
  </tbody>
</table>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSimonView Question on Stackoverflow
Solution 1 - AngularPhilipp KiefView Answer on Stackoverflow
Solution 2 - AngularTobiiView Answer on Stackoverflow
Solution 3 - AngularJoshua Michael CalafellView Answer on Stackoverflow
Solution 4 - AngularBraden BrownView Answer on Stackoverflow
Solution 5 - AngularNashirView Answer on Stackoverflow