Cell Expansion
Cell Expansion is a feature to display any component or template under a row. The trigger is from a cell in which the column definition has expansion
property defined. It is a small button in the left hand side or right of the cell. Below is the way to define an expansion:
{ field: 'country', expansion: {
//component or template defined using ng-template
component: NestedTableComponent,
//optional. Put button at the start / end of the cell
buttonPosition: 'end',
//optional. Call back to enable/disable expansion for each row
isDisabled?: (row: People) => row.country !== 'ID'
}
}
Each columns can have an expansion. In below example, Email column has expansion to display a form to send an email. Country column has one to display detail table and Action column (the edit button) can display an edit form programmatically.
Philippines | |||||||||
Kazakhstan | |||||||||
United States | |||||||||
Philippines | |||||||||
Indonesia | |||||||||
Kazakhstan | |||||||||
China | |||||||||
Greece | |||||||||
Netherlands | |||||||||
Sweden | |||||||||
Cuba | |||||||||
Philippines | |||||||||
Brazil | |||||||||
Macedonia | |||||||||
Peru | |||||||||
Russia | |||||||||
Mongolia | |||||||||
Georgia | |||||||||
Indonesia | |||||||||
Indonesia | |||||||||
Jamaica | |||||||||
Russia | |||||||||
Ukraine | |||||||||
Ukraine | |||||||||
Sweden | |||||||||
China | |||||||||
China | |||||||||
China | |||||||||
Sweden | |||||||||
Argentina | |||||||||
Philippines | |||||||||
China | |||||||||
China | |||||||||
France | |||||||||
China | |||||||||
France | |||||||||
China | |||||||||
Thailand | |||||||||
Poland | |||||||||
China | |||||||||
Philippines | |||||||||
China | |||||||||
Brazil | |||||||||
Indonesia | |||||||||
Macedonia | |||||||||
France | |||||||||
Ukraine | |||||||||
Oman | |||||||||
Iran | |||||||||
Morocco | |||||||||
Poland | |||||||||
Armenia | |||||||||
Bulgaria | |||||||||
France | |||||||||
Colombia | |||||||||
Afghanistan | |||||||||
Russia | |||||||||
Vietnam | |||||||||
Philippines | |||||||||
China | |||||||||
Japan | |||||||||
Portugal | |||||||||
Poland | |||||||||
Ivory Coast | |||||||||
Hungary | |||||||||
Bulgaria | |||||||||
Mexico | |||||||||
Vietnam | |||||||||
China | |||||||||
France | |||||||||
China | |||||||||
China | |||||||||
Russia | |||||||||
Spain | |||||||||
Ivory Coast | |||||||||
China | |||||||||
Senegal | |||||||||
Ukraine | |||||||||
Indonesia | |||||||||
Russia | |||||||||
Albania | |||||||||
Poland | |||||||||
Czech Republic | |||||||||
Syria | |||||||||
Kyrgyzstan | |||||||||
China | |||||||||
Croatia | |||||||||
Poland | |||||||||
China | |||||||||
China | |||||||||
China | |||||||||
Philippines | |||||||||
Panama | |||||||||
China | |||||||||
Denmark | |||||||||
China | |||||||||
China | |||||||||
China | |||||||||
Czech Republic | |||||||||
China | |||||||||
China | |||||||||
China | |||||||||
Morocco | |||||||||
Pakistan | |||||||||
Peru | |||||||||
China | |||||||||
Nigeria | |||||||||
Netherlands | |||||||||
France | |||||||||
Russia | |||||||||
Indonesia | |||||||||
Philippines | |||||||||
Poland | |||||||||
Poland | |||||||||
Tunisia | |||||||||
Indonesia | |||||||||
Indonesia | |||||||||
China | |||||||||
Indonesia | |||||||||
Brazil | |||||||||
Philippines | |||||||||
Pakistan | |||||||||
Brazil | |||||||||
Ukraine | |||||||||
Russia | |||||||||
China | |||||||||
Indonesia | |||||||||
Greece | |||||||||
Indonesia | |||||||||
Philippines | |||||||||
Indonesia | |||||||||
Sierra Leone | |||||||||
Afghanistan | |||||||||
China | |||||||||
Canada | |||||||||
France | |||||||||
Brazil | |||||||||
Poland | |||||||||
China | |||||||||
China | |||||||||
Haiti | |||||||||
China | |||||||||
Thailand | |||||||||
France | |||||||||
Indonesia | |||||||||
China | |||||||||
China | |||||||||
South Africa | |||||||||
United States | |||||||||
Brazil | |||||||||
Indonesia | |||||||||
Russia | |||||||||
China | |||||||||
Madagascar | |||||||||
Bosnia and Herzegovina | |||||||||
Indonesia | |||||||||
Finland | |||||||||
China | |||||||||
Portugal | |||||||||
Nigeria | |||||||||
Georgia | |||||||||
Poland | |||||||||
Greece | |||||||||
China | |||||||||
Brazil | |||||||||
Mexico | |||||||||
Nigeria | |||||||||
Bangladesh | |||||||||
Argentina | |||||||||
Indonesia | |||||||||
Burkina Faso | |||||||||
Russia | |||||||||
Ukraine | |||||||||
Mexico | |||||||||
Poland | |||||||||
China | |||||||||
Honduras | |||||||||
Micronesia | |||||||||
Indonesia | |||||||||
China | |||||||||
Russia | |||||||||
France | |||||||||
Brazil | |||||||||
Philippines | |||||||||
China | |||||||||
Egypt | |||||||||
Spain | |||||||||
New Zealand | |||||||||
Philippines | |||||||||
Poland | |||||||||
Poland | |||||||||
Indonesia | |||||||||
Eritrea | |||||||||
Brazil | |||||||||
Afghanistan | |||||||||
Poland | |||||||||
Indonesia | |||||||||
Japan | |||||||||
China | |||||||||
Indonesia | |||||||||
Peru | |||||||||
China | |||||||||
Ukraine | |||||||||
Russia | |||||||||
China | |||||||||
China | |||||||||
Portugal | |||||||||
United States | |||||||||
Portugal | |||||||||
Saudi Arabia | |||||||||
Philippines | |||||||||
South Africa | |||||||||
China | |||||||||
Portugal | |||||||||
Poland | |||||||||
China | |||||||||
China | |||||||||
Iran | |||||||||
Guatemala | |||||||||
Belize | |||||||||
Brazil | |||||||||
Guatemala | |||||||||
Indonesia | |||||||||
Thailand | |||||||||
South Africa | |||||||||
Norway | |||||||||
Brazil | |||||||||
Thailand | |||||||||
Portugal | |||||||||
Serbia | |||||||||
Japan | |||||||||
Madagascar | |||||||||
Indonesia | |||||||||
Mexico | |||||||||
Argentina | |||||||||
Indonesia | |||||||||
Japan | |||||||||
Malaysia | |||||||||
Indonesia | |||||||||
China | |||||||||
China | |||||||||
Russia | |||||||||
Greece | |||||||||
Ireland | |||||||||
China | |||||||||
China | |||||||||
China | |||||||||
France | |||||||||
Argentina | |||||||||
Greece | |||||||||
China | |||||||||
Ukraine | |||||||||
Mauritania | |||||||||
Indonesia | |||||||||
China | |||||||||
Israel | |||||||||
Japan | |||||||||
New Zealand | |||||||||
Russia | |||||||||
Indonesia | |||||||||
Czech Republic | |||||||||
China | |||||||||
Norway | |||||||||
Serbia | |||||||||
China | |||||||||
Indonesia |
The email is using ng-template
. We can specify variables binding to relevant row, column and close function. For example:
<ng-template #rowDetail1 let-row="row" let-column="column" let-close="close">
<div class="grid grid-cols-[1fr_auto] gap-4 p-4 w-[300px]">
<!-- Display email address -->
<div class="col-span-2">Send to </div>
...
<!-- collapse the expansion when Send button is clicked -->
<button (click)=close()>Send</button>
</div>
</ng-template>
If using angular component, the component should implements
interface. Below is the source code of the Country cell expansion:
import { Component, computed, Input, OnInit, Signal } from "@angular/core";
import { PanemuTableComponent , PanemuTableController , PanemuTableDataSource , PanemuTableService , PropertyColumn , ExpansionRowRenderer , TableQuery } from "ngx-panemu-table";
import { People } from "../../model/people";
import { DataService } from "../../service/data.service";
@Component({
templateUrl: 'nested-table.component.html',
imports: [PanemuTableComponent ],
standalone: true
})
export class NestedTableComponent implements OnInit, ExpansionRowRenderer <People> {
row!: People;
column!: PropertyColumn <People>;
close!: Function;
columns = this.pts.buildColumns<People>([
{ field: 'id' },
{ field: 'name' },
{ field: 'email' },
{ field: 'country' },
])
datasource = new PanemuTableDataSource <People>
controller = PanemuTableController .createWithCustomDataSource<People>(this.columns, this.loadData.bind(this));
rowCount = computed(() => {
if (this.controller.getAllDataAsSignal()) {
return this.controller.getData().length
}
return 0
})
constructor(private pts: PanemuTableService , private dataService: DataService) { }
ngOnInit() {
this.dataService.getPeople().subscribe({
next: people => {
this.datasource.setData(people);
this.controller.reloadData();
}
})
}
private loadData(startIndex: number, maxRows: number, tableQuery: TableQuery ) {
tableQuery.tableCriteria = [{field: 'country', value: this.row.country}];
return this.datasource.getData(startIndex, maxRows, tableQuery);
}
}
The Edit People expansion is a bit different. It doesn't have the default button to trigger the expansion. Instead the button is a custom one with pencil icon. This is achieved by providing
to the column definition. Then the pencil button click event is bound to
method.
Below is the ng-template
for the pencil button:
<ng-template #actionCellTemplate let-column="column" let-row="row">
<div class="flex justify-between">
<button class="action-button" (click)="edit(row)"><span class="material-symbols-outlined text-base leading-5 block">edit</span></button>
</div>
</ng-template>
Below is the edit
function in the .ts file
edit(row: People) {
this.controller.expand(row, this.clmEditInExpansion)
}
And finally here is the PeopleFormComponent
rendered inside the expansion.
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit, signal } from '@angular/core';
import { PanemuBusyIndicatorComponent , PropertyColumn , ExpansionRowRenderer } from 'ngx-panemu-table';
import { People } from '../../model/people';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-people-form',
templateUrl: 'people-form.component.html',
standalone: true,
imports: [PanemuBusyIndicatorComponent , ReactiveFormsModule],
})
export class PeopleFormComponent implements OnInit, ExpansionRowRenderer <People> {
row!: People;
column!: PropertyColumn <People>;
close!: Function;
ready = signal(false);
form = this.fb.group({
name: [''],
email: ['']
})
constructor(private fb: FormBuilder) {}
ngOnInit() {
setTimeout(() => {
this.ready.set(true);
}, 1000);
this.form.setValue({
name: this.row.name,
email: this.row.email || ''
})
}
save() {
this.row.name = this.form.controls.name.value || ''
this.row.email = this.form.controls.email.value || ''
this.close()
}
}