Add a column to an object-based dataset
In this tutorial, you will add a column to an object-based dataset through a custom context menu item. You will learn why the built-in column-insert items are unavailable for object data and how to extend the columns setting at runtime.
/* file: app.component.ts */import { Component, ViewChild } from '@angular/core';import { GridSettings, HotTableComponent, HotTableModule } from '@handsontable/angular-wrapper';import type Handsontable from 'handsontable/base';
/* start:skip-in-preview */const data: Handsontable.RowObject[] = [ { sku: 'SKU-4821', supplier: 'Harbor Goods', stock: 142, category: 'Electronics' }, { sku: 'SKU-0093', supplier: 'Alpine Supply Co.', stock: 0, category: 'Apparel' }, { sku: 'SKU-7311', supplier: 'Meadow Foods', stock: 67, category: 'Grocery' }, { sku: 'SKU-2250', supplier: 'Harbor Goods', stock: 318, category: 'Electronics' }, { sku: 'SKU-9047', supplier: 'Northwind Traders', stock: 12, category: 'Hardware' }, { sku: 'SKU-6638', supplier: 'Alpine Supply Co.', stock: 205, category: 'Apparel' },];/* end:skip-in-preview */
type ColumnConfig = { data: string; width: number; className?: string };
// `columns` and `colHeaders` are kept in mutable variables so the custom menu// item can extend them and push the change back through `updateSettings`.const columns: ColumnConfig[] = [ { data: 'sku', width: 120 }, { data: 'supplier', width: 170 }, { data: 'stock', width: 90 }, { data: 'category', width: 130 },];const colHeaders = ['SKU', 'Supplier', 'Stock', 'Category'];
let newColumnCount = 0;
@Component({ standalone: true, imports: [HotTableModule], selector: 'example1-add-column-object-data', template: ` <div> <hot-table [data]="data" [settings]="gridSettings"></hot-table> </div> `,})export class AppComponent { @ViewChild(HotTableComponent, { static: false }) readonly hotTable!: HotTableComponent;
readonly data: Handsontable.RowObject[] = data;
readonly gridSettings: GridSettings = { rowHeaders: true, colHeaders, columns, height: 'auto', width: '100%', contextMenu: { items: { add_column: { name: 'Add column', callback: (_key: string, selection: Array<{ start: { col: number } }>) => { const hot = this.hotTable?.hotInstance;
if (!hot) return;
const insertAt = selection[0].start.col + 1;
newColumnCount += 1; const newKey = `custom_${newColumnCount}`;
(hot.getSourceData() as Array<Record<string, unknown>>).forEach((row) => { row[newKey] = ''; });
columns.splice(insertAt, 0, { data: newKey, width: 130, className: 'ht-new-column' }); colHeaders.splice(insertAt, 0, `Custom ${newColumnCount}`);
hot.updateSettings({ columns, colHeaders }); }, }, sep1: { name: '---------' }, row_above: { name: 'Insert row above' }, row_below: { name: 'Insert row below' }, remove_row: { name: 'Remove row' }, }, }, };}/* end-file */
/* file: app.config.ts */import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';import { registerAllModules } from 'handsontable/registry';import { HOT_GLOBAL_CONFIG, HotGlobalConfig, NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';
registerAllModules();
export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), { provide: HOT_GLOBAL_CONFIG, useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig, }, ],};/* end-file */<div> <example1-add-column-object-data></example1-add-column-object-data></div>td.ht-new-column { background-color: #e3f2fd;}Overview
Difficulty: Intermediate Time: ~15 minutes
When your data is an array of objects, each column maps to an object property through the columns setting. The built-in Insert column left and Insert column right menu items rely on alter('insert_col_start'), which Handsontable rejects for object data:
Cannot create new column. When data source in an object, you can only have as much columns as defined in first data row, data schema or in the ‘columns’ setting.
To add a column over object data, you extend the columns setting yourself, add the matching property to every row, and re-apply the configuration with updateSettings. This recipe wires that into a custom Add column menu item.
Before you begin
You need a working Handsontable instance with an object-based dataset. If you are starting fresh, install it first:
npm install handsontableThen import and register all modules:
import Handsontable from 'handsontable/base';import { registerAllModules } from 'handsontable/registry';
registerAllModules();Set up the object dataset and column configuration
const data = [{ sku: 'SKU-4821', supplier: 'Harbor Goods', stock: 142, category: 'Electronics' },// ...more rows];const columns = [{ data: 'sku', width: 120 },{ data: 'supplier', width: 170 },{ data: 'stock', width: 90 },{ data: 'category', width: 130 },];const colHeaders = ['SKU', 'Supplier', 'Stock', 'Category'];What’s happening:
datais an array of row objects. Each object key (sku,supplier, …) maps to one column.columnsbinds each column to a property through itsdatakey. The column order follows this array, not the order of keys inside the row objects.- Keep
columnsandcolHeadersin variables you can mutate. The custom menu item edits these arrays and feeds them back throughupdateSettings.
Configure the context menu without the built-in column items
contextMenu: {items: {add_column: { /* defined in Step 3 */ },sep1: '---------',row_above: { name: 'Insert row above' },row_below: { name: 'Insert row below' },remove_row: { name: 'Remove row' },},},What’s happening:
- Passing an object to
contextMenu.itemslists the exact items that appear. The built-incol_leftandcol_rightkeys are omitted because they throw on object data. add_columnis a custom key that replaces them with logic that works for object data.'---------'is the predefined separator token. It renders a visual divider between the custom action and the built-in row operations.
- Passing an object to
Implement “Add column”
let newColumnCount = 0;// inside contextMenu.itemsadd_column: {name: 'Add column',callback(key, selection) {const insertAt = selection[0].start.col + 1;newColumnCount += 1;const newKey = `custom_${newColumnCount}`;// 1. Add the new property to every source row object.hot.getSourceData().forEach((row) => {row[newKey] = '';});// 2. Extend the `columns` and `colHeaders` arrays at the clicked position.columns.splice(insertAt, 0, { data: newKey, width: 130, className: 'ht-new-column' });colHeaders.splice(insertAt, 0, `Custom ${newColumnCount}`);// 3. Apply the new structure.hot.updateSettings({ columns, colHeaders });},},What’s happening:
selection[0].start.colis the visual index of the right-clicked column. Adding1inserts the new column directly to its right; use the value as-is to insert to its left.newColumnCountproduces a unique property key (custom_1,custom_2, …) so each new column binds to a fresh object property. Use a counter rather than a random value to keep keys predictable.hot.getSourceData()returns the array of row objects. Addingrow[newKey] = ''gives every row an empty value for the new column, so the cells start blank and editable.columns.splice(insertAt, 0, ...)inserts the new column descriptor, andcolHeaders.splice(...)inserts its header. TheclassNamemarks the new column for styling.hot.updateSettings({ columns, colHeaders })re-applies the configuration. Because each row object now has the new property, the column renders with the data already in place.
How it works - complete flow
- The user right-clicks a cell. Handsontable opens the context menu with the custom Add column item and the selected built-in row items.
- Add column computes the insert position from the clicked column, generates a unique property key, and adds that property to every row object.
- The
columnsandcolHeadersarrays gain a matching entry at the same position. updateSettingsre-maps the columns. The new column appears with a highlighted header background and accepts edits like any other column.
What you learned
- Object data cannot use the built-in
col_left/col_rightitems, becausealter('insert_col_*')is rejected when the data source is an object. - Omit those keys from
contextMenu.itemsand add a custom item instead. - To add a column over object data: add the property to each row object, extend
columnsandcolHeaders, then callupdateSettings. - Column order follows the
columnsarray, sosplicecontrols where the new column appears. selection[0].start.colgives you the right-clicked column for positioning.
Next steps
- Set
dataSchemaso rows added later also include the new property. - Add a
disabled()function to the menu item to limit how many columns users can add. - Explore the Context menu guide for the full list of built-in item keys and advanced configuration.