Lattice Grid Buy a licence

demo D260

Real-Time Operations Dashboard: One Feed, Many Grids in Angular

One feed drives orders, shipments and incidents grids and a throughput chart, with no backend

createDataRouter · MockWebSocket · opsFeed

This is a ready-made operations console: one feed drives an orders grid, a shipments grid and an incidents grid plus a throughput chart, all with no backend. It is a working starting point you can lift and point at real data, showing several live views sharing one source.

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, signal, inject, OnDestroy } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { createGrid, createHeadlessGrid, type Grid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import { createChart } from '@toclocoinc/lattice-grid/modules/charts';
import { createDataRouter } from '@toclocoinc/lattice-grid/modules/data-router';
import { MockWebSocket, opsFeed } from '@toclocoinc/lattice-grid/modules/mock-socket';
import { LatticeGridComponent, LatticeChartComponent, LatticeRouter, provideLattice, provideLatticeRouter } from '@toclocoinc/lattice-grid/angular';

const MONEY = { style: 'currency', currency: 'USD', decimals: 0 };

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [LatticeGridComponent, LatticeChartComponent],
  // Scoped to this component: built once, destroyed with it.
  providers: [provideLatticeRouter({ key: 'type', rowKey: 'id' })],
  template:
    '<div style="display:grid;grid-template-columns:repeat(3, 1fr);gap:14px">' +
    '  <lattice-grid route="order" [config]="orderConfig" (gridReady)="onOrders($event)" style="display:block;height:300px"></lattice-grid>' +
    '  <lattice-grid route="shipment" [config]="shipmentConfig" style="display:block;height:300px"></lattice-grid>' +
    '  <lattice-grid route="incident" [config]="incidentConfig" (gridReady)="onIncidents($event)" style="display:block;height:300px"></lattice-grid>' +
    '</div>' +
    '@if (metricsGrid(); as m) {' +
    '  <lattice-chart [grid]="m" [config]="chartConfig" style="display:block;height:200px;margin-top:14px"></lattice-chart>' +
    '}',
})
export class AppComponent implements OnDestroy {
  private routerService = inject(LatticeRouter);
  private socket?: MockWebSocket;
  private ordersGrid: Grid | null = null;
  private incidentsGrid: Grid | null = null;

  metricsGrid = signal<Grid | null>(null);
  chartConfig = { type: 'line', x: 't', y: 'events', title: 'Throughput per tick' };

  orderConfig = {
    rowKey: 'id', selection: { mode: 'multiple', checkbox: true },
    columns: [
      { field: 'id', title: 'Order', layout: { width: 96 } },
      { field: 'region', title: 'Region', filter: { type: 'set' } },
      { field: 'customer', title: 'Customer' },
      { field: 'value', title: 'Value', type: 'number', format: MONEY, total: 'sum' },
    ],
  };
  shipmentConfig = {
    rowKey: 'id', columns: [
      { field: 'id', title: 'Shipment', layout: { width: 96 } },
      { field: 'region', title: 'Region', filter: { type: 'set' } },
      { field: 'carrier', title: 'Carrier' },
      { field: 'units', title: 'Units', type: 'number', total: 'sum' },
    ],
  };
  incidentConfig = {
    rowKey: 'id', columns: [
      { field: 'id', title: 'Incident', layout: { width: 96 } },
      { field: 'region', title: 'Region', filter: { type: 'set' } },
      { field: 'service', title: 'Service' },
      { field: 'severity', title: 'Severity' },
    ],
  };

  constructor() {
    // A headless grid holds the metric series; the chart reads it.
    const metrics = createHeadlessGrid({ rowKey: 't', columns: [
      { field: 't', type: 'number' }, { field: 'events', type: 'number' },
    ] });
    this.routerService.attach(metrics, 'metric', { rowKey: 't' });
    this.metricsGrid.set(metrics);

    // The mock socket runs it with no backend; one line swaps in a real
    // WebSocket.
    this.socket = new MockWebSocket({ feed: opsFeed({ seed: 7 }), rate: 900, jitter: 300 });
    this.socket.onmessage = (event) => {
      const message = JSON.parse(event.data);
      if (message.kind === 'snapshot') this.routerService.router.load(message.rows);
      else this.routerService.router.apply(message.changes);
    };
  }

  private maybeLink() {
    if (this.ordersGrid && this.incidentsGrid) {
      // link narrows Incidents to the regions of the orders you select,
      // re-pushed through the same keyed-diff path.
      this.routerService.router.link(this.ordersGrid, this.incidentsGrid, { from: 'region', to: 'region' });
    }
  }
  onOrders(grid: Grid) { this.ordersGrid = grid; this.maybeLink(); }
  onIncidents(grid: Grid) { this.incidentsGrid = grid; this.maybeLink(); }

  ngOnDestroy() { this.socket?.close(); }
}

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