Lattice Grid Buy a licence

angular data grid · excel export

Angular Grid: Excel Export

grid.export.excel() writes a real, styled workbook in the browser: frozen panes, a named sheet, banded row fills. Wire it to the built-in toolbar action, your own button, or both, reached through the component's own grid property.

Also in: React

Install and import

npm install @toclocoinc/lattice-grid
# free on localhost; a production domain needs a licence key (see /pricing/)

The file

supplier-charges.component.ts

// supplier-charges.component.ts
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';

@Component({
  selector: 'app-supplier-charges',
  standalone: true,
  imports: [LatticeGridComponent],
  template:
    '<button (click)="download()">Download .xlsx</button>' +
    '<lattice-grid [config]="grid" style="display:block;height:460px;margin-top:8px"></lattice-grid>',
})
export class SupplierChargesComponent {
  @ViewChild(LatticeGridComponent) gridRef!: LatticeGridComponent;

  // The workbook is written in the browser with no dependency: a zip
  // container, the sheet, the shared strings and the styles, assembled by
  // the grid. The rail's Excel button takes the defaults; the named one
  // sets the options and writes a real frozen header.
  grid = {
    rowKey: 'id',
    selection: 'multiple',
    toolPanel: {
      side: 'left',
      panels: ['columns', 'filters', 'quick'],
      exportName: 'supplier-charges',
      actions: [
        'undo', 'redo', 'restore', 'maximise', '-',
        'excel',
        {
          name: 'excel-named',
          title: 'Download .xlsx, named sheet with a frozen header',
          icon: 'spreadsheet',
          run: ({ grid }: any) => grid.export.excel({
            download: true, fileName: 'supplier-charges-q3', sheetName: 'Charges',
            freezePanes: true, variantFills: true,
          }),
        },
        'clipboard',
      ],
    },
    columns: [
      { field: 'ref', title: 'Invoice', layout: { pin: 'start', width: 140 } },
      { field: 'supplier', title: 'Supplier', layout: { flex: 1, min: 200 }, filter: { type: 'set' } },
      { field: 'region', title: 'Region', filter: { type: 'set' } },
      { field: 'category', title: 'Category', filter: { type: 'set' } },
      { field: 'quantity', title: 'Qty', type: 'number', layout: { width: 110 } },
      { field: 'amount', title: 'Amount', type: 'number', layout: { width: 150 },
        format: { style: 'currency', currency: 'GBP', decimals: 2 }, total: 'sum', filter: { type: 'number' } },
      { field: 'note', title: 'Note', layout: { width: 280 } },
    ],
    rows: [], // supplier charge records, one per invoice line
  };

  // The same writer, reached through the component: a named sheet with a
  // frozen header, saved from your own button.
  download() {
    this.gridRef.grid.export.excel({
      download: true, fileName: 'supplier-charges-q3', sheetName: 'Charges',
      freezePanes: true, variantFills: true,
    });
  }
}

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

See it running

Building…
Loading a live grid…

Open this demo's Angular tab →

Working in Angular

Two routes to the same writer. The toolbar's built-in 'excel' action downloads with the grid's defaults; a named custom action or your own button both call grid.export.excel(options) directly, reached here through a classic @ViewChild(LatticeGridComponent) query (a signal viewChild() works just as well).

Only what the reader can see. Export follows the current filter and sort by default, so a filtered view exports the filtered rows, not the full dataset behind it.

The query resolves after the first render. gridRef is set once Angular has run change detection over the template, which is already true by the time a reader can click Download, so no lifecycle hook is needed around the call itself.

Related