Usages Cell Expansion

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.

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 ExpansionRowRenderer interface. Below is the source code of the Country cell expansion:

nested-table.component.ts
nested-table.component.html
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 cellRenderer to the column definition. Then the pencil button click event is bound to PanemuTableController.expand() 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.

people-form.component.ts
people-form.component.html
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()
  }
}