Lattice Grid Buy a licence

demo D145

The row types in Angular

Leaf, group, footer, grand total, detail, synthesised and pinned, in one grid

Row.group · detail · grandTotal

This grid shows all the row kinds in one place: leaf, group, footer, grand total, detail, synthesised and pinned. It shows that a dataset rarely fits one row shape, and how the grid draws each kind so a hierarchy, its totals and its detail all read together.

Building…
Loading a live grid…

This is the Angular version. A standalone component takes the whole configuration through one config input, surfaces grid events as outputs, and exposes the live grid on a getter for anything the inputs do not cover.

The configuration

import { Component, ViewChild } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import { LatticeGridComponent, provideLattice } from '@toclocoinc/lattice-grid/angular';

const money = { style: 'currency', currency: 'USD', decimals: 2 };

// Expanding a row opens its charge lines in a nested grid.
const detail = {
  rows: (row: any) => row.data.charges,
  height: 160,
  config: {
    rowKey: 'line',
    columns: [
      { field: 'line', title: 'Charge line', layout: { width: 200 } },
      { field: 'quantity', title: 'Qty', type: 'number', layout: { width: 100 } },
      { field: 'amount', title: 'Amount', type: 'number', format: money, total: 'sum' },
    ],
  },
};

const columns = [
  { field: 'account', title: 'Account', filter: { type: 'set' }, group: { enabled: true, index: 0 } },
  { field: 'unit', title: 'Unit', filter: { type: 'set' }, layout: { width: 150 } },
  { field: 'service', title: 'Service', filter: { type: 'set' }, layout: { width: 150 } },
  { field: 'region', title: 'Region', filter: { type: 'set' }, layout: { width: 140 } },
  { field: 'cost', title: 'Monthly cost', type: 'number', format: money, total: 'sum', layout: { width: 180 } },
];

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [LatticeGridComponent],
  template: '<lattice-grid [config]="grid" (grid-ready)="onGridReady()" style="display:block;height:540px"></lattice-grid>',
})
export class AppComponent {
  @ViewChild(LatticeGridComponent) gridRef!: LatticeGridComponent;

  grid = {
    rowKey: 'id',
    selection: 'multiple',
    groupFooter: true,
    grandTotalRow: true,
    detail,
    toolPanel: { side: 'left', panels: ['columns', 'filters', 'views', 'quick'],
      actions: ['undo', 'redo', 'export', 'restore', 'maximise'], exportName: 'lattice-demo' },
    columns,
    rows: [/* allocations, each carrying its own charges array */],
  };

  // Open one detail row on arrival: it is one of the row kinds this grid exists
  // to show, so it should be visible without a click. Leaf rows, group headings,
  // group footers and an inline grand total round out the set.
  // The grid is created once the view has rendered, so the live instance
  // arrives on grid-ready rather than in ngAfterViewInit.
  onGridReady() {
    const grid = this.gridRef.grid;
    let leaf = null;
    for (let i = 0; i < grid.rows.count() && !leaf; i++) {
      const row = grid.rows.get(i);
      if (row && !row.group && !row.detail) leaf = row;
    }
    if (leaf) grid.detail.open(leaf.key);
  }
}

bootstrapApplication(AppComponent, {
  providers: [provideLattice({ createGrid })],
});

A single dataset rarely fits one row shape. Leaf rows carry the raw records, group rows summarise the leaves beneath them, footer and grand total rows roll figures up to a subtotal or a final sum, detail rows expand a leaf into a nested view, and synthesised rows exist only in the grid to carry a computed value that has no backing record. Pinned rows sit outside the scrollable body entirely, fixed above or below it. A developer reaches for this mix when a report needs subtotals per group alongside a running total, or when a master row needs an inline detail panel without navigating away. Row.group, Row.detail and Row.grandTotal mark a row’s kind so the grid can render, sort and export each one according to its role rather than treating every row as an equal leaf. Because row kind is a property of the row rather than a separate rendering path, virtual scrolling treats leaf, group and footer rows as one continuous list, so scroll position and row recycling work the same regardless of how many kinds are mixed into the current view. Each row kind keeps its correct role and aria-level in the accessibility tree, so a screen reader announces a group heading as a heading and a leaf as a data row, not as indistinguishable table cells.

How do you show subtotals and a grand total in a JavaScript data grid?

Mark the rows that should aggregate using Row.group for a per-group subtotal and Row.grandTotal for the overall figure, and the grid computes and renders those values from the leaf rows beneath them. Pinned rows can hold a grand total at the top or bottom of the viewport so it stays visible while the body scrolls.