@polycyphers/spreadsheet-angular
v1.0.5
Published
Angular wrapper around @polycyphers/spreadsheet-core — virtual-scrolled spreadsheet with Excel I/O, formula engine, sorting, filtering, and selection.
Maintainers
Readme
@polycyphers/spreadsheet-angular
Angular (21+) wrapper exposing the same spreadsheet engine as @polycyphers/spreadsheet — standalone components, signal-driven, OnPush, with 50+ inputs and 20+ outputs.
Commercial. Production use requires a license — unlicensed instances render a visible watermark. A free 30-day trial is available, email-gated, no credit card. Details + pricing at packages.polycyphers.com/spreadsheet.
Install
npm install @polycyphers/spreadsheet-angularWhat's included
React parity (from @polycyphers/spreadsheet)
- Main component
ks-spreadsheet(SpreadsheetComponent) — standalone, OnPush, signal-driven. ~50 inputs + ~20 outputs. - Sub-components (all standalone, exported for independent use):
ks-sheet-tabs,ks-formula-bar,ks-status-bar,ks-filter-dropdown,ks-context-menu,ks-long-text-dialog,ks-pagination,ks-license-watermark, and four cell editors:ks-cell-text-editor,ks-cell-select-editor,ks-cell-checkbox-editor,ks-overflow-cell-content. - Virtual scroll via core
VirtualScrollEngine. - Sorting + filtering — click headers to sort; filter icon opens a per-column dropdown.
- Column resize — drag the right edge of any header.
- Inline editing — double-click or F2; text/number/date/select/checkbox editors; Tab/Enter/Arrow navigation; Escape cancels.
- Formula bar — displays current A1 reference; edits flow back to the cell.
- Clipboard —
Ctrl+C/Ctrl+V(tab-delimited; respects column types; batched into undo history). - Undo / redo —
Ctrl+Z/Ctrl+Y(+Ctrl+Shift+Z). - Selection range with live status-bar metrics (count / sum / avg / min / max).
- Status bar with density toggle + accent color picker + help tooltip.
- Sheet tabs with badges, icons, disabled state.
- Long-text dialog opened via
longTextOverflowaffordance on columns. - Context menu — right-click with configurable items (static array or
(ctx) => items). - Cell formatting —
formatdescriptor on columns:text,number,currency,percentage,date. - Row templates via
TemplateRefinputs —toolbarLeft,slotBelowHeader,footer, plussummaryRowrender function. - Features container — inline row creation, hour validation, cost code dialog, calculated fields, dirty tracking, dynamic columns, multi-row selection, position summary, context menu.
- Plugin system —
CorePlugin<T>(identical contract to React). Built-in:createPointagePlugin,createTasksPlugin. Helpers:createPlugin,composePlugins. - Excel I/O —
exportToExcel,importFromExcel,exportToCSVon the component (ExcelJS is a peer dep, lazy-loaded). - Pinning —
pinning: { columns, rows }input with CSS-sticky rendering, or per-columnpinned: trueflag. - Merged cells —
mergedCellsinput accepted by coreMergeCalculator(rendering path visible only if cells are anchors). - Mode —
view/edit/createwith editability gating. - License system —
setLicense(key),isLicensed(),KARAMENT_INTERNAL_KEY,LicenseWatermarkComponent(dormant, enforcement flag off). - Imperative API on the component —
exportToExcel,importFromExcel,exportToCSV,undo,redo,resetColumnWidths,getData,scrollToRow,selectCell,selectRange,clearSelection,focus,getFormulaEngine. Access via@ViewChild(SpreadsheetComponent).
TOMWEB-specific (not in React package)
densityMode(comfortable|compact) +accentColor(blue|purple|teal|amber|rose) with runtime toggle in status bar.customActions— toolbar buttons above the formula bar.dataServiceinput — auto-save pattern; debouncedsaveCell(change)calls on every cell edit.enablePagination+rowsPerPage— page-based rendering alongside virtual scroll.formulaDialogRequestoutput — fired when a row asks for the formula builder dialog.pasteDetectedoutput — rawClipboardEventfor custom paste handling.metricsUpdateoutput — fires whenever the selection metrics change.rowReorderoutput — ready for drag-drop reorder wiring.TENDER_FUNCTIONS(SELLING_PRICE,ONP_PERCENT,PROFIT_UP_WEIGHT,PROFIT_WEIGHT,TOTAL_SELLING) — importable or auto-registered via[registerTenderFunctions]="true".CellIdAdapter— converts{sheetId}-{rowIndex}-{columnKey}↔'SheetName'!A1.TOMWEB_CELL_ACCESSOR— pre-built accessor for the{ id, cells: { [key]: { value, … } } }row shape. Pass as[cellAccessor].- Config presets —
TENDER_RESOURCES_PRESET,TENDER_MATERIALS_PRESET,STANDARD_FORMULAS_PRESET,BOQ_EDITOR_PRESET. - DI tokens —
SPREADSHEET_CELL_ACCESSOR,SPREADSHEET_DATA_SERVICE,SPREADSHEET_FEATURE_DEFAULTS(for app-wide defaults). - Dirty tracking —
features.dirtyTracking: { enabled, onDirtyChange }; dirty row ids flow to status bar + row styling. - Calculated fields —
features.calculatedFields: [{ targetColumn, calculate, dependencies, format }].
ExcelJS is optional — only required if you call exportToExcel / importFromExcel.
Usage
Basic (flat row shape — matches React package default)
import { Component } from '@angular/core';
import {
SpreadsheetComponent,
SpreadsheetColumn,
SpreadsheetChange
} from '@polycyphers/spreadsheet-angular';
interface Item { code: string; description: string; qty: number; unit: string; }
@Component({
selector: 'app-boq',
standalone: true,
imports: [SpreadsheetComponent],
template: `
<ks-spreadsheet
[rows]="items"
[columns]="columns"
[showStatusBar]="true"
(cellChange)="onCellChange($event)"
/>
`
})
export class BoqPage {
items: Item[] = [{ code: '01.01', description: 'Concrete', qty: 100, unit: 'm³' }];
columns: SpreadsheetColumn<Item>[] = [
{ key: 'code', label: 'Code', width: 100, editable: true },
{ key: 'description', label: 'Description', width: 300, editable: true },
{ key: 'qty', label: 'Qty', width: 80, type: 'number', editable: true, align: 'right' },
{ key: 'unit', label: 'Unit', width: 80, editable: false }
];
onCellChange(e: SpreadsheetChange<Item>) { /* ... */ }
}TOMWEB row shape (migrating from app-sam-table-spreadsheet)
import { TOMWEB_CELL_ACCESSOR, TENDER_RESOURCES_PRESET } from '@polycyphers/spreadsheet-angular';
@Component({ /* ... */ })
export class TenderManpowerPage {
columns = [
{ key: 'ref', label: 'Ref', width: 100, editable: true },
{ key: 'name', label: 'Name', width: 280, editable: true },
{ key: 'rate', label: 'Hourly Rate', width: 120, type: 'number',
format: { type: 'currency', currency: 'USD', decimals: 2 },
align: 'right', editable: true }
];
rows: any[] = [
{ id: 1, cells: { ref: { value: 'MP-001' }, name: { value: 'Foreman' }, rate: { value: 35 } } }
];
}<ks-spreadsheet
[rows]="rows"
[columns]="columns"
[cellAccessor]="TOMWEB_CELL_ACCESSOR"
[densityMode]="'compact'"
[accentColor]="'blue'"
[enablePagination]="true"
[rowsPerPage]="50"
[registerTenderFunctions]="true"
(cellChange)="onCellEdit($event)"
(rowReorder)="onRowReorder($event)"
(formulaDialogRequest)="openFormulaBuilder($event)" />Auto-save via dataService
import { SpreadsheetDataService } from '@polycyphers/spreadsheet-angular';
class TenderSaveService implements SpreadsheetDataService<Item> {
debounceMs = 500;
async saveCell(change) { await fetch('/api/cell', { method: 'PUT', body: JSON.stringify(change) }); }
}<ks-spreadsheet [dataService]="saveService" ... />Tender formula functions
// Option A — auto-register via input
<ks-spreadsheet [registerTenderFunctions]="true" ... />
// Option B — import & register on your own engine
import { registerTenderFunctions, FormulaEngine } from '@polycyphers/spreadsheet-angular';
const engine = new FormulaEngine();
registerTenderFunctions(engine);
engine.setCellValue('C1', '=SELLING_PRICE(A1, B1)');CellIdAdapter (TOMWEB id ↔ A1)
import { CellIdAdapter } from '@polycyphers/spreadsheet-angular';
const adapter = new CellIdAdapter({
resolveSheet(ref) {
const sheet = sheets.find((s) => typeof ref === 'number' ? s.id === ref : s.name === ref);
if (!sheet) return null;
return {
sheetId: sheet.id,
sheetName: sheet.name,
columnIndex: (key) => sheet.columns.findIndex((c) => c.key === key),
columnKey: (idx) => sheet.columns[idx]?.key ?? null
};
},
activeSheetId: () => activeSheet.id
});
adapter.toA1('5-10-qty'); // "'Trade A'!D11"
adapter.fromA1(`'Trade A'!D11`); // "5-10-qty"Build
npm run build # ng-packagr → dist/
npm run type-checkMigration checklist (TOMWEB sam-table-spreadsheet → this package)
- Replace imports:
SamTableSpreadsheetComponent→SpreadsheetComponent. - Map
[sheets]→ render oneks-spreadsheetper sheet OR manage active sheet viasheetTabs+activeSheetTab. - Pass
[cellAccessor]="TOMWEB_CELL_ACCESSOR"so the component readsrow.cells[key].value. - Rename event bindings:
(cellEdit)→(cellChange),(rowAction)→(rowChange),(sheetChange)shape unchanged. - Replace
[config]preset object with discrete inputs ([enablePagination],[rowsPerPage],[densityMode], etc.) OR use the exported*_PRESETconstants and spread them. - Set
[registerTenderFunctions]="true"if your sheets useSELLING_PRICE/ONP_PERCENT/ etc. - Wire
(formulaDialogRequest)if you were using the formula builder dialog. - Wire
(metricsUpdate)if you were using status-bar metrics outside the component. - If you relied on
dataServicefor auto-save, pass it via[dataService](same interface). - Test — the core engine is shared with React, so formulas, undo/redo, virtual scroll, clipboard, and selection metrics behave identically.
Architecture
┌────────────────────────────────────────────────────────┐
│ @polycyphers/spreadsheet (React, v1.0.11) │
│ @polycyphers/spreadsheet-angular (Angular, v0.1.0, NEW) │
│ (future) @polycyphers/spreadsheet-vue │
└────────────────────┬───────────────────────────────────┘
│ depends on (peer)
▼
┌────────────────────────────────────────────────────────┐
│ @polycyphers/spreadsheet-core │
│ Pure TS — no framework, no DOM deps │
│ - FormulaEngine (68 built-ins + custom) │
│ - VirtualScrollEngine / SortEngine / FilterEngine │
│ - SelectionManager / ClipboardManager / UndoRedo │
│ - PinningCalculator / MergeCalculator / StatusBar │
│ - PluginManager │
│ - ExcelIO (optional ExcelJS peer dep) │
└────────────────────────────────────────────────────────┘Known limitations (v0.1.0)
- Full pinning split layout — columns with
pinned: trueor withinpinning.columnsuse CSSposition: stickyrather than a true split-pane render. Works for up to a few pinned columns. - Merged cell rendering —
mergedCellsis accepted and fed to the coreMergeCalculator, but visual rowspan/colspan merging isn't implemented yet (non-anchor cells still render). - Row-height resize drag handle — not yet exposed; per-row
heightis accepted but not drag-resizable. - Row drag-drop reorder UI — the
rowReorderoutput is wired, but no drag-handle is rendered yet. - Multi-range (Ctrl+Click) selection — single range only; non-contiguous selection not supported.
- Inline row creation UI —
features.inlineRowCreationconfig is consumed byapplyInlineRowCreation()helper, but the built-in "+ new row" button isn't rendered yet. - Cost code dialog — config type is defined; consumer wires the dialog via
formulaDialogRequestor a customTemplateRef.
These are UI-level only — all logic is already in core and available to wire up.
