Search code examples
angularhtml-tablematerial-ui

Combines Angular Material table-expandable-rows and table-wrapped, and adds custom expand details but cannot show the custom detail content


I tried to create a component that combines table-expandable-row and table-wrapped, and use ngTemplateOutletContext to add custom details, but it does not work.

Stackblitz: https://stackblitz.com/edit/test-table-expandable-rows-with-table-wrapper

How can I fix it?


Solution

  • When I changed ContentChild to

      @ContentChild('temp', {
        read: TemplateRef,
      })
      template: TemplateRef<any>;
    

    It started working, so we need to be specific with the selector. Also promote the ng-template to the top of the content children in HTML.

    Full Code:

    Table Example HTML:

    <app-table-expandable-rows
      [dataSource]="dataSource"
      [columns]="displayedColumns"
      matSort
      #sort="matSort"
    >
      <!-- Custom Detail -->
      <ng-template let-data #temp>
        <div>{{ data | json }}</div>
      </ng-template>
      <!-- Custom column -->
      <ng-container matColumnDef="name">
        <th mat-header-cell *matHeaderCellDef>Name</th>
        <td mat-cell *matCellDef="let element">{{ element.name }}</td>
      </ng-container>
    
      <!-- Custom column -->
      <ng-container matColumnDef="position">
        <th mat-header-cell *matHeaderCellDef mat-sort-header>No.</th>
        <td mat-cell *matCellDef="let element">{{ element.position }}</td>
      </ng-container>
    
      <!-- Custom column -->
      <ng-container matColumnDef="weight">
        <th mat-header-cell *matHeaderCellDef mat-sort-header>Weight</th>
        <td mat-cell *matCellDef="let element">{{ element.weight }}</td>
      </ng-container>
    
      <!-- Custom column -->
      <ng-container matColumnDef="symbol">
        <th mat-header-cell *matHeaderCellDef>Symbol</th>
        <td mat-cell *matCellDef="let element">{{ element.symbol }}</td>
      </ng-container>
    </app-table-expandable-rows>
    

    Table Example HTML:

    import { CommonModule } from '@angular/common';
    import { Component, ViewChild, AfterViewInit } from '@angular/core';
    import { MatTableModule, MatTableDataSource } from '@angular/material/table';
    import { MatSort, MatSortModule } from '@angular/material/sort';
    import { TableExpandableRowsComponent } from '../table-expandable-rows/table-expandable-rows.component';
    
    export interface PeriodicElement {
      name: string;
      position: number;
      weight: number;
      symbol: string;
      description: string;
    }
    
    const ELEMENT_DATA: PeriodicElement[] = [
      {
        position: 1,
        name: 'Hydrogen',
        weight: 1.0079,
        symbol: 'H',
        description: `Hydrogen is a chemical element with symbol H and atomic number 1. With a standard
            atomic weight of 1.008, hydrogen is the lightest element on the periodic table.`,
      },
      {
        position: 2,
        name: 'Helium',
        weight: 4.0026,
        symbol: 'He',
        description: `Helium is a chemical element with symbol He and atomic number 2. It is a
            colorless, odorless, tasteless, non-toxic, inert, monatomic gas, the first in the noble gas
            group in the periodic table. Its boiling point is the lowest among all the elements.`,
      },
      {
        position: 3,
        name: 'Lithium',
        weight: 6.941,
        symbol: 'Li',
        description: `Lithium is a chemical element with symbol Li and atomic number 3. It is a soft,
            silvery-white alkali metal. Under standard conditions, it is the lightest metal and the
            lightest solid element.`,
      },
      {
        position: 4,
        name: 'Beryllium',
        weight: 9.0122,
        symbol: 'Be',
        description: `Beryllium is a chemical element with symbol Be and atomic number 4. It is a
            relatively rare element in the universe, usually occurring as a product of the spallation of
            larger atomic nuclei that have collided with cosmic rays.`,
      },
      {
        position: 5,
        name: 'Boron',
        weight: 10.811,
        symbol: 'B',
        description: `Boron is a chemical element with symbol B and atomic number 5. Produced entirely
            by cosmic ray spallation and supernovae and not by stellar nucleosynthesis, it is a
            low-abundance element in the Solar system and in the Earth's crust.`,
      },
      {
        position: 6,
        name: 'Carbon',
        weight: 12.0107,
        symbol: 'C',
        description: `Carbon is a chemical element with symbol C and atomic number 6. It is nonmetallic
            and tetravalent—making four electrons available to form covalent chemical bonds. It belongs
            to group 14 of the periodic table.`,
      },
      {
        position: 7,
        name: 'Nitrogen',
        weight: 14.0067,
        symbol: 'N',
        description: `Nitrogen is a chemical element with symbol N and atomic number 7. It was first
            discovered and isolated by Scottish physician Daniel Rutherford in 1772.`,
      },
      {
        position: 8,
        name: 'Oxygen',
        weight: 15.9994,
        symbol: 'O',
        description: `Oxygen is a chemical element with symbol O and atomic number 8. It is a member of
             the chalcogen group on the periodic table, a highly reactive nonmetal, and an oxidizing
             agent that readily forms oxides with most elements as well as with other compounds.`,
      },
      {
        position: 9,
        name: 'Fluorine',
        weight: 18.9984,
        symbol: 'F',
        description: `Fluorine is a chemical element with symbol F and atomic number 9. It is the
            lightest halogen and exists as a highly toxic pale yellow diatomic gas at standard
            conditions.`,
      },
      {
        position: 10,
        name: 'Neon',
        weight: 20.1797,
        symbol: 'Ne',
        description: `Neon is a chemical element with symbol Ne and atomic number 10. It is a noble gas.
            Neon is a colorless, odorless, inert monatomic gas under standard conditions, with about
            two-thirds the density of air.`,
      },
    ];
    
    @Component({
      selector: 'app-table-example',
      standalone: true,
      imports: [
        CommonModule,
        MatSortModule,
        MatTableModule,
        TableExpandableRowsComponent,
      ],
      templateUrl: './table-example.component.html',
      styleUrl: './table-example.component.css',
    })
    export class TableExampleComponent implements AfterViewInit {
      displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
      elementData = ELEMENT_DATA;
      dataSource = new MatTableDataSource<PeriodicElement>(ELEMENT_DATA);
    
      @ViewChild('sort') sort: MatSort;
    
      ngAfterViewInit() {
        this.dataSource.sort = this.sort;
      }
    }
    

    Table Expandable Rows TS:

    import { DataSource } from '@angular/cdk/collections';
    import { CommonModule } from '@angular/common';
    import {
      Component,
      ContentChildren,
      ContentChild,
      ViewChild,
      Input,
      AfterContentInit,
      QueryList,
      effect,
      input,
      computed,
      TemplateRef,
    } from '@angular/core';
    import {
      MatColumnDef,
      MatHeaderRowDef,
      MatNoDataRow,
      MatRowDef,
      MatTable,
      MatTableModule,
      MatTableDataSource,
    } from '@angular/material/table';
    import { MatIconModule } from '@angular/material/icon';
    import { MatButtonModule } from '@angular/material/button';
    import {
      animate,
      state,
      style,
      transition,
      trigger,
    } from '@angular/animations';
    
    @Component({
      selector: 'app-table-expandable-rows',
      standalone: true,
      imports: [CommonModule, MatTableModule, MatIconModule, MatButtonModule],
      templateUrl: './table-expandable-rows.component.html',
      styleUrl: './table-expandable-rows.component.scss',
      animations: [
        trigger('detailExpand', [
          state('collapsed,void', 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 TableExpandableRowsComponent<T> implements AfterContentInit {
      @ContentChildren(MatHeaderRowDef) headerRowDefs: QueryList<MatHeaderRowDef>;
      @ContentChildren(MatRowDef) rowDefs: QueryList<MatRowDef<T>>;
      @ContentChildren(MatColumnDef) columnDefs: QueryList<MatColumnDef>;
      @ContentChild(MatNoDataRow) noDataRow: MatNoDataRow;
      @ContentChild('temp')
      template: TemplateRef<any>;
    
      @ViewChild(MatTable, { static: true }) table: MatTable<T>;
    
      @Input() dataSource: DataSource<T>;
      columns = input<string[]>([]);
    
      expandedElement: any;
    
      columnsToDisplayWithExpand = computed<string[]>(() => {
        return [...this.columns(), 'expand'];
      });
    
      ngAfterContentInit() {
        this.columnDefs.forEach((columnDef) => this.table.addColumnDef(columnDef));
        this.rowDefs.forEach((rowDef) => this.table.addRowDef(rowDef));
        this.headerRowDefs.forEach((headerRowDef) =>
          this.table.addHeaderRowDef(headerRowDef)
        );
        this.table.setNoDataRow(this.noDataRow);
      }
    }
    

    Table Expandable Rows HTML:

    <table
      mat-table
      [dataSource]="dataSource"
      multiTemplateDataRows
      class="mat-elevation-z8"
    >
      <ng-content></ng-content>
    
      <ng-container matColumnDef="expand">
        <th mat-header-cell *matHeaderCellDef aria-label="row actions">&nbsp;</th>
        <td mat-cell *matCellDef="let element">
          <button
            mat-icon-button
            aria-label="expand row"
            (click)="
              expandedElement = expandedElement === element ? null : element;
              $event.stopPropagation()
            "
          >
            @if (expandedElement === element) {
            <mat-icon>keyboard_arrow_up</mat-icon>
            } @else {
            <mat-icon>keyboard_arrow_down</mat-icon>
            }
          </button>
        </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]="columnsToDisplayWithExpand().length"
        >
          <div
            class="example-element-detail"
            [@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'"
          >
            <!-- <div class="example-element-diagram">
              <div class="example-element-position">{{ element.position }}</div>
              <div class="example-element-symbol">{{ element.symbol }}</div>
              <div class="example-element-name">{{ element.name }}</div>
              <div class="example-element-weight">{{ element.weight }}</div>
            </div>
            <div class="example-element-description">
              {{ element.description }}
              <span class="example-element-description-attribution">
                -- Wikipedia8
              </span>
            </div> -->
            <ng-template
              [ngTemplateOutlet]="template"
              [ngTemplateOutletContext]="{ $implicit: element }"
            >
            </ng-template>
          </div>
        </td>
      </ng-container>
    
      <tr mat-header-row *matHeaderRowDef="columnsToDisplayWithExpand()"></tr>
      <tr
        mat-row
        *matRowDef="let element; columns: columnsToDisplayWithExpand()"
        class="example-element-row"
        [class.example-expanded-row]="expandedElement === element"
        (click)="expandedElement = expandedElement === element ? null : element"
      ></tr>
      <tr
        mat-row
        *matRowDef="let row; columns: ['expandedDetail']"
        class="example-detail-row"
      ></tr>
    
      <tr class="mat-row" *matNoDataRow>
        <td class="mat-cell" colspan="4">No data</td>
      </tr>
    </table>
    

    Stackblitz Demo