demo D226
Joining a second grid in Angular
Orders carry a customer id but no tier; a join brings it across from a customers grid, then groups revenue by tier
join: { with, on, select }
Here orders carry a customer id but no tier, and a join brings the tier across from a separate customers grid, then groups revenue by it. It lets you enrich one dataset from another and report on the combined result, without flattening the two together beforehand.
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 { CommonModule } from '@angular/common';
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 USD = { style: 'currency', currency: 'USD', decimals: 0 };
// The two sides: orders carry a customerId, and the tier lives on the customer.
const customerColumns = [
{ field: 'id', title: 'ID' },
{ field: 'name', title: 'Customer' },
{ field: 'tier', title: 'Tier' },
];
const orderColumns = [
{ field: 'customerId', title: 'Customer' },
{ field: 'region', title: 'Region', filter: { type: 'set' } },
{ field: 'amount', title: 'Value', type: 'number', format: USD, total: 'sum' },
];
const byTierColumns = [
{ field: 'tier', title: 'Tier' },
{ field: 'revenue', title: 'Revenue', type: 'number', format: USD },
{ field: 'orders', title: 'Orders', type: 'number' },
];
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, LatticeGridComponent],
template:
'<div style="display:flex;gap:16px">' +
' <lattice-grid #orders [config]="ordersGrid" (grid-ready)="onOrdersReady()" style="flex:2;height:300px"></lattice-grid>' +
' <lattice-grid #customers [config]="customersGrid" (grid-ready)="onCustomersReady()" style="flex:1;height:300px"></lattice-grid>' +
'</div>' +
'<lattice-grid *ngIf="byTier" [config]="byTier" style="display:block;margin-top:16px"></lattice-grid>',
})
export class AppComponent {
@ViewChild('orders') ordersRef!: LatticeGridComponent;
@ViewChild('customers') customersRef!: LatticeGridComponent;
byTier: any = null;
private ordersReady = false;
private customersReady = false;
ordersGrid = { rowKey: 'id', toolPanel: { side: 'left', panels: ['filters', 'columns'] }, columns: orderColumns, rows: [/* orders */] };
customersGrid = { rowKey: 'id', columns: customerColumns, rows: [/* customers */] };
// Both grids are created once their own view has rendered, so each live
// instance arrives on its own grid-ready rather than in ngAfterViewInit;
// the join waits until both sides have reported in.
onOrdersReady() { this.ordersReady = true; this.buildByTier(); }
onCustomersReady() { this.customersReady = true; this.buildByTier(); }
// The join brings tier across from the customers grid onto each order, matching
// customerId to id, then the group runs on that brought-across field. Both sides
// are live instances, so the roll-up follows the filters on the orders grid.
private buildByTier() {
if (!this.ordersReady || !this.customersReady) return;
const orders = this.ordersRef.grid;
const customers = this.customersRef.grid;
this.byTier = {
autoHeight: true,
columns: byTierColumns,
source: {
mode: 'derived', from: orders, follow: 'filtered', refresh: 'live',
join: { with: customers, on: { left: 'customerId', right: 'id' }, select: ['tier'] },
groupBy: 'tier',
select: { revenue: { of: 'amount', fn: 'sum' }, orders: { fn: 'count' } },
sort: [{ col: 'revenue', dir: 'desc' }],
},
};
}
}
bootstrapApplication(AppComponent, {
providers: [provideLattice({ createGrid })],
});