Getting Started Introduction

Introduction




Logo Logo



NgxPanemuTable is an Angular table component. It is designed to be easy to use. Most work will be in typescript file, while the html file only needs to have a very simple panemu-table tag.

Features

  • Inline editing.

  • Declarative table definition in typescript. Very little code in html needed.

  • Sane defaults that can be overriden app-wide and per table basis.

  • Pagination. More flexible than common pagination.

    • Previous page of first page overflow to last page and vice versa.
    • User can input arbitrary range.
    • Client or server side. Client side pagination is provided, server side implementation is up to you.
    • Developer can create custom pagination component. See Custom Pagination.
  • Filtering.

    • Query builder supports nested AND/OR predicates.
    • Provided ready to use client-side sorting, filtering, pagination and grouping
    • Filter behavior can be customized. See Global Search.
  • Row Grouping.

    • Support group level pagination.
    • Customizable row group header and footer. See Custom Row Group.
  • Column header

  • Column Resizable.

  • User can change column visibility, position and stickiness at runtime. See PanemuSettingComponent.

  • Save table states:

    • Columns visibility, position and stickiness.
    • Filter, grouping, sorting and pagination.

    If user changes any of those, go to other and back, the states are restored. See Persist States.

  • Customizable table cell.

  • Sticky column, header and footer. The footer usage example can be seen in Virtual Scroll.

  • Cell colspan and rowspan. See Cell Spanning.

  • Export to CSV. See PanemuTableController.getCsvData().

  • Handle huge data using virtual scroll. Now it doesn't support variable row height. But it will in the future.

This is a quick demo of PanemuTableComponent, PanemuPaginationComponent, PanemuQueryComponent and PanemuSettingComponent.

TypeScript
HTML
SCSS
import { Component, inject, OnInit, signal, TemplateRef, viewChild } from '@angular/core';
import { ComputedColumn, DefaultCellRenderer, DefaultRowGroupRenderer, PanemuPaginationComponent, PanemuQueryComponent, PanemuSettingComponent, PanemuTableComponent, PanemuTableController, PanemuTableDataSource, PanemuTableService, Predicate, TickColumnController } from 'ngx-panemu-table';
import { People } from '../model/people';
import { DataService } from '../service/data.service';
import { FilterCountryCellComponent } from './custom-cell/filter-country-cell.component';
import { PeopleFormComponent } from './custom-cell/people-form.component';

@Component({
    templateUrl: 'all-features-client.component.html',
    imports: [PanemuTableComponent, PanemuPaginationComponent, PanemuQueryComponent, PanemuSettingComponent],
    styleUrl: 'all-features-client.component.scss'
})

export class AllFeaturesClientComponent implements OnInit {
  genderMap = signal({});
  sendEmailTemplate = viewChild<TemplateRef<any>>('sendEmailTemplate');
  actionCellTemplate = viewChild<TemplateRef<any>>('actionCellTemplate');
  countryFooter = viewChild<TemplateRef<any>>('countryFooter');
  clmAction: ComputedColumn<People> = {
    type: 'computed',
    formatter: (val: any) => '',
    cellRenderer: DefaultCellRenderer.create(this.actionCellTemplate),
    expansion: { component: PeopleFormComponent },
    sticky: 'end',
    width: 80,
    resizable: false,
    cellStyle: (_: string) => 'border-left-color: rgba(0,0,0, .12); border-left-width: 1px; border-left-style: solid;'
  };
  pts = inject(PanemuTableService);
  dataService = inject(DataService);
  columns = this.pts.buildColumns<People>([
    { type:'tick', width: 50, controller: new TickColumnController() },
    { field: 'id', type: 'int', width: 60},
    { field: 'name', width: 150 },
    {
      field: 'email', width: 230, expansion: {
        component: this.sendEmailTemplate,
        isDisabled: (row) => {
          return row.country == 'ID'
        },
      }
    },
    { field: 'gender', width: 80, type: 'map', valueMap: this.genderMap },
    {
      field: 'country', width: 150, type: 'map', valueMap: this.dataService.getCountryMap(),
      cellRenderer: FilterCountryCellComponent.create(this.onCountryFilterClick.bind(this)),
      rowGroupRenderer: DefaultRowGroupRenderer.create({ footerComponent: this.countryFooter })
    },
    { field: 'amount', width: 100, type: 'decimal' },
    {
      type: 'group', label: 'Date Info', children: [
        { field: 'enrolled', width: 150, type: 'date' },
        { field: 'last_login', width: 180, type: 'datetime' },
      ]
    },
    { field: 'verified', width: 80, type: 'boolean' },
    this.clmAction,
  ]
  )
  datasource = new PanemuTableDataSource<People>;
  controller = PanemuTableController.create<People>(this.columns, this.datasource);

  ngOnInit() {
    //set initial grouping
    this.controller.groupByColumns = [{ field: 'country' }]

    //set inital filtering
    this.controller.criteria = { field: 'verified', value: true, type: 'eq' } as Predicate;

    this.dataService.getPeople().subscribe(result => {
      this.datasource.setData(result);
      this.controller.reloadData();
    });

    //pretend genderMap is taken from server data
    setTimeout(() => {
      this.genderMap.set({ F: 'Female', M: 'Male' })
    }, 1000);
  }

  onCountryFilterClick(value: any, propName: string) {
    this.controller.criteria = { field: propName, value: value, type: 'eq' } as Predicate;
    this.controller.reloadData();
  }

  edit(row: People) {
    this.controller.expand(row, this.clmAction)
  }

  delete(row: People) {
    alert('Delete ' + JSON.stringify(row))

  }

}

More In The Future

These features are not developed yet. Please create a ticket in our repository if you need them so we know what to prioritize.

  • Virtual scroll with variable row height.

Releases:

v.0.7.0

  • [New] Query builder component supports AND/OR nested predicates.
  • [Breaking Change] Tick column API overhaul. TickColumnClass is removed. Create tick columns with { type: 'tick', controller: new TickColumnController<T>() } and read the selection via controller.tickedRowsSignal (now a getter property, not the old getTickedRowsAsSignal() method). See Tick Column.
  • [Breaking Change] CellFormatter is now generic — CellFormatter<T> — so the rowData and column parameters are typed.
  • [Breaking Change] ComputedColumn and GroupedColumn are now generic (ComputedColumn<T>, GroupedColumn<T>). The internal NonGroupColumn<T> union type was removed.
  • [Breaking Change] New LeafColumn<T> interface. CellComponent<T> and HeaderComponent now declare column: LeafColumn<T> (was PropertyColumn<T>). Custom cell/header renderers should update their column field type to LeafColumn<T> so they also work with computed, tick and map columns.
  • [Breaking Change] MapColumn.type: 'map' is now required (was optional).
  • [Breaking Change] Row grouping now keeps the original data type of the grouped value. RowGroupData.value is no longer always coerced to string. If you were comparing rowGroup.data.value === 'someString' against a boolean or numeric field, update the comparison.

v.0.6.6

  • Support Angular 21.

v.0.6.4

  • Multi-column sorting with Ctrl key.
  • Escape special characters in search highlight regex.

v.0.6.0

  • Support Angular 20

v.0.5.0

v.0.4.0

  • [Breaking Change] Persist state now has a way to pick what states to save rather than always saving all states. See this page.
  • Icons can be changed. See Icons

v.0.3.0

  • Inline editing

v.0.2.0

  • Transpose selected row.

v.0.0.9

  • New PanemuSettingComponent as the UI to change columns visibility, position and stickiness.
  • Save table states (column structure, pagination, filtering, sorting and grouping)
  • Support cell rowspan and colspan using RowRenderer.

v.0.0.7

  • Virtual scroll
  • Table footer
  • RowGroup now customizable and can have footer

Support Us

Buy Me a Coffee at ko-fi.com

About Panemu

We are software development company. Contact us if you want to build a top notch software.