Skip to content

Use the EmptyDataState plugin to display a contextual overlay when the grid has no data or all rows are hidden by active filters.

Prerequisites

To use the Empty Data State plugin, import it from Handsontable:

To use the filter-aware empty state (which automatically detects when all rows are hidden by filters), also enable the Filters plugin alongside emptyDataState.

Overview

The Empty Data State plugin provides a user-friendly overlay system for Handsontable when there’s no data to display. It automatically detects when your table is empty or when all data is hidden by filters, and displays an appropriate message with optional action buttons. It automatically integrates with the Filters plugin to provide context-aware messages and actions.

Basic configuration

To enable the Empty Data State plugin, set the emptyDataState option to true or provide a configuration object.

TypeScript
/* file: app.component.ts */
import { Component } from '@angular/core';
import { GridSettings, HotTableModule } from '@handsontable/angular-wrapper';
@Component({
selector: 'app-example1',
standalone: true,
imports: [HotTableModule],
template: `
<hot-table
[settings]="hotSettings"
[data]="hotData"
>
</hot-table>
`,
})
export class AppComponent {
readonly hotData = [];
readonly hotSettings: GridSettings = {
height: 'auto',
colHeaders: true,
rowHeaders: true,
navigableHeaders: true,
dropdownMenu: true,
filters: true,
emptyDataState: true, // Enable empty data state with default settings
};
}
/* 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';
// register Handsontable's modules
registerAllModules();
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
{
provide: HOT_GLOBAL_CONFIG,
useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig,
},
],
};
/* end-file */
HTML
<app-example1></app-example1>

Custom configuration

The empty data state supports customization of the title, description, and action buttons.

TypeScript
/* file: app.component.ts */
import { Component, ViewChild } from '@angular/core';
import { GridSettings, HotTableComponent, HotTableModule } from '@handsontable/angular-wrapper';
@Component({
selector: 'app-example2',
standalone: true,
imports: [HotTableModule],
template: `
<hot-table
#hotTable
[settings]="hotSettings"
[data]="hotData"
>
</hot-table>
`,
})
export class AppComponent {
@ViewChild('hotTable') hotTable!: HotTableComponent;
readonly hotData = [];
readonly hotSettings: GridSettings = {
height: 'auto',
colHeaders: ['First Name', 'Last Name', 'Email'],
rowHeaders: true,
navigableHeaders: true,
dropdownMenu: true,
filters: true,
emptyDataState: {
message: {
title: 'No data available',
description: 'Please add some data to get started.',
buttons: [
{
text: 'Add Sample Data',
type: 'primary',
callback: () => {
this.hotTable.hotInstance!.loadData([
['John', 'Doe', 'john@example.com'],
['Jane', 'Smith', 'jane@example.com'],
['Bob', 'Johnson', 'bob@example.com'],
['Alice', 'Johnson', 'alice@example.com'],
]);
}
}
]
}
},
};
}
/* 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';
// register Handsontable's modules
registerAllModules();
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
{
provide: HOT_GLOBAL_CONFIG,
useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig,
},
],
};
/* end-file */
HTML
<app-example2></app-example2>

Dynamic messages based on source

You can provide different messages based on the source of the empty state (e.g., filters vs. no data). This allows for more contextual user guidance.

TypeScript
/* file: app.component.ts */
import { Component, ViewChild } from '@angular/core';
import { GridSettings, HotTableComponent, HotTableModule } from '@handsontable/angular-wrapper';
@Component({
selector: 'app-example3',
standalone: true,
imports: [HotTableModule],
template: `
<hot-table
#hotTable
[settings]="hotSettings"
[data]="hotData"
>
</hot-table>
`,
})
export class AppComponent {
@ViewChild('hotTable') hotTable!: HotTableComponent;
readonly hotData = [];
readonly hotSettings: GridSettings = {
height: 'auto',
colHeaders: ['First Name', 'Last Name', 'Email'],
rowHeaders: true,
navigableHeaders: true,
dropdownMenu: true,
filters: true,
contextMenu: true,
emptyDataState: {
message: (source) => {
switch (source) {
case 'filters':
return {
title: 'No results found',
description: 'Your current filters are hiding all results. Try adjusting your search criteria.',
buttons: [
{
text: 'Clear Filters',
type: 'secondary',
callback: () => {
const filtersPlugin = this.hotTable.hotInstance!.getPlugin('filters');
if (filtersPlugin) {
filtersPlugin.clearConditions();
filtersPlugin.filter();
}
}
}
]
};
default:
return {
title: 'No data available',
description: 'There\'s nothing to display yet. Add some data to get started.',
buttons: [
{
text: 'Add Sample Data',
type: 'primary',
callback: () => {
this.hotTable.hotInstance!.loadData([
['John', 'Doe', 'john@example.com'],
['Jane', 'Smith', 'jane@example.com'],
['Bob', 'Johnson', 'bob@example.com'],
['Alice', 'Johnson', 'alice@example.com'],
]);
}
}
]
};
}
}
},
};
}
/* 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';
// register Handsontable's modules
registerAllModules();
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
{
provide: HOT_GLOBAL_CONFIG,
useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig,
},
],
};
/* end-file */
HTML
<app-example3></app-example3>

Localize empty data state

Translate default empty data state labels using the global translations mechanism. The empty data state plugin introduces the following keys to the language dictionary that you can use to translate the empty state UI:

KeyDefault Value
EMPTY_DATA_STATE_TITLE'No data available'
EMPTY_DATA_STATE_DESCRIPTION'There's nothing to display yet.'
EMPTY_DATA_STATE_TITLE_FILTERS'No results found'
EMPTY_DATA_STATE_DESCRIPTION_FILTERS'It looks like your current filters are hiding all results.'
EMPTY_DATA_STATE_BUTTONS_FILTERS_RESET'Reset filters'

To learn more about the translation mechanism, see the Languages guide.

Result

After enabling the plugin, the grid displays a centered overlay message whenever there is no data to show. When the Filters plugin is also enabled, the overlay automatically switches to a filter-specific message and shows a “Reset filters” button when all rows are hidden by active filter conditions.