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?
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.
<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>
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;
}
}
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
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"> </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>