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.

Id
Name
Email
Country

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 } 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],

  //OnPush change detection is normally not needed. It is here due to interference from NgDoc lib
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PeopleFormComponent implements OnInit, ExpansionRowRenderer<People> {
  row!: People;
  column!: PropertyColumn<People>;
  close!: Function;
  ready = false;

  form = this.fb.group({
    name: [''],
    email: ['']
  })

  constructor(private fb: FormBuilder, private cdr: ChangeDetectorRef) {}

  ngOnInit() {
    this.ready = true;
    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()
  }
}