Searching values
Enable the Search plugin and call query() on each keystroke to highlight matching cells across the grid.
The Search plugin lets you scan all cells in the grid and get back a list of matches. Enable it by setting the search option to true or to a configuration object.
Once enabled, the plugin exposes the query(queryStr) method. Call it with a search string whenever the user types. By default, the search is case-insensitive and matches partial cell values.
Simplest use case
The example below:
- Enables the
Searchplugin by settingsearchtotrue - Listens for
keyupevents on a search input - Calls
query()on each keystroke and re-renders the grid to apply highlighting
/* file: app.component.ts */import { Component, ViewChild } from '@angular/core';import { GridSettings, HotTableComponent, HotTableModule} from '@handsontable/angular-wrapper';
@Component({ selector: 'example1-searching-values', standalone: true, imports: [HotTableModule], template: ` <div class="example-controls-container"> <div class="controls"> <input id="search_field" type="search" placeholder="Search" (keyup)="searchFieldKeyup($event)" /> </div> </div> <div> <hot-table [data]="data" [settings]="gridSettings"></hot-table> </div>`,})export class AppComponent { @ViewChild(HotTableComponent, { static: false }) readonly hotTable!: HotTableComponent;
readonly data: Array<Array<string | number>> = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'], ];
readonly gridSettings: GridSettings = { colHeaders: true, search: true, height: 'auto', autoWrapRow: true, autoWrapCol: true };
searchFieldKeyup(event: KeyboardEvent): void { const hot = this.hotTable?.hotInstance; // get the `Search` plugin's instance const search = hot?.getPlugin('search'); // use the `Search` plugin's `query()` method const queryResult = search?.query((event.target as any).value);
console.log(queryResult);
hot?.render(); }}/* 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 modulesregisterAllModules();
export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), { provide: HOT_GLOBAL_CONFIG, useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig, }, ],};/* end-file */<div> <example1-searching-values></example1-searching-values></div>Custom search result class
You can style search results with a custom CSS class, using the Search plugin’s searchResultClass option.
The example below highlights search results with a pink background and red text. To do this, it:
- Defines a custom CSS class called
my-class, scoped to.ht-theme-main .handsontable - Enables the
Searchplugin with a configuration object - Sets
searchResultClassto'my-class'
/* file: app.component.ts */import { Component, ViewChild, ViewEncapsulation } from '@angular/core';import { GridSettings, HotTableComponent, HotTableModule} from '@handsontable/angular-wrapper';
@Component({ selector: 'example2-searching-values', standalone: true, imports: [HotTableModule], template: ` <div class="example-controls-container"> <div class="controls"> <input id="search_field2" type="search" placeholder="Search" (keyup)="searchFieldKeyup($event)" /> </div> </div> <div> <hot-table [data]="data" [settings]="gridSettings"></hot-table> </div>`, styles: `.ht-theme-main .handsontable .my-class { background: pink; color: red; }`, encapsulation: ViewEncapsulation.None,})export class AppComponent { @ViewChild(HotTableComponent, { static: false }) readonly hotTable!: HotTableComponent;
readonly data: Array<Array<string | number>> = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'], ];
readonly gridSettings: GridSettings = { colHeaders: true, search: { searchResultClass: 'my-class', }, height: 'auto', autoWrapRow: true, autoWrapCol: true };
searchFieldKeyup(event: KeyboardEvent): void { const hot = this.hotTable?.hotInstance; // get the `Search` plugin's instance const search = hot?.getPlugin('search'); // use the `Search` plugin's `query()` method const queryResult = search?.query((event.target as any).value);
console.log(queryResult);
hot?.render(); }}/* 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 modulesregisterAllModules();
export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), { provide: HOT_GLOBAL_CONFIG, useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig, }, ],};/* end-file */<div> <example2-searching-values></example2-searching-values></div>Custom query method
You can replace the built-in substring search with a custom query method, using the queryMethod option.
The example below searches only for exact matches. To do this, it:
- Defines a custom query method called
onlyExactMatchthat uses strict equality (===) - Enables the
Searchplugin with a configuration object - Sets
queryMethodtoonlyExactMatch
/* file: app.component.ts */import { Component, ViewChild } from '@angular/core';import { GridSettings, HotTableComponent, HotTableModule} from '@handsontable/angular-wrapper';
@Component({ selector: 'example3-searching-values', standalone: true, imports: [HotTableModule], template: ` <div class="example-controls-container"> <div class="controls"> <input id="search_field3" type="search" placeholder="Search" (keyup)="searchFieldKeyup($event)" /> </div> </div> <div> <hot-table [data]="data" [settings]="gridSettings"></hot-table> </div>`,})export class AppComponent { @ViewChild(HotTableComponent, { static: false }) readonly hotTable!: HotTableComponent;
readonly data: Array<Array<string | number>> = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'], ];
readonly gridSettings: GridSettings = { colHeaders: true, search: { // add your custom query method queryMethod: this.onlyExactMatch, }, height: 'auto', autoWrapRow: true, autoWrapCol: true };
searchFieldKeyup(event: KeyboardEvent): void { const hot = this.hotTable?.hotInstance; // get the `Search` plugin's instance const search = hot?.getPlugin('search'); // use the `Search` plugin's `query()` method const queryResult = search?.query((event.target as any).value);
console.log(queryResult);
hot?.render(); }
// define your custom query method onlyExactMatch( queryStr: { toString: () => any }, value: { toString: () => any } ): boolean { return queryStr.toString() === value.toString(); }}/* 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 modulesregisterAllModules();
export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), { provide: HOT_GLOBAL_CONFIG, useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig, }, ],};/* end-file */<div> <example3-searching-values></example3-searching-values></div>Custom callback
You can add a custom callback function, using the Search plugin’s callback option.
The example below displays the number of matching search results. To do this, it:
- Defines a custom callback function called
searchResultCounterthat counts matches and calls the default callback to preserve highlighting - Enables the
Searchplugin with a configuration object - Sets
callbacktosearchResultCounter
/* file: app.component.ts */import { Component, ViewChild } from '@angular/core';import { GridSettings, HotTableComponent, HotTableModule} from '@handsontable/angular-wrapper';import Handsontable from 'handsontable/base';
@Component({ selector: 'example4-searching-values', standalone: true, imports: [HotTableModule], template: ` <div class="example-controls-container"> <div class="controls"> <input id="search_field3" type="search" placeholder="Search" (keyup)="searchFieldKeyup($event)" /> </div> <output class="console" id="output"> {{ resultCount }} results </output> </div>
<div> <hot-table [data]="data" [settings]="gridSettings"></hot-table> </div>`,})export class AppComponent { @ViewChild(HotTableComponent, { static: false }) readonly hotTable!: HotTableComponent;
readonly data: Array<Array<string | number>> = [ ['Tesla', 2017, 'black', 'black'], ['Nissan', 2018, 'blue', 'blue'], ['Chrysler', 2019, 'yellow', 'black'], ['Volvo', 2020, 'yellow', 'gray'], ];
readonly gridSettings: GridSettings = { colHeaders: true, search: { // add your custom callback function callback: ( _instance: Handsontable, _row: number, _col: number, _value: Handsontable.CellValue, result: boolean ) => this.searchResultCounter(_instance, _row, _col, _value, result), }, height: 'auto', autoWrapRow: true, autoWrapCol: true };
resultCount = 0;
searchFieldKeyup(event: KeyboardEvent): void { this.setResultCounter(0);
const search = this.hotTable?.hotInstance?.getPlugin('search'); const queryResult = search?.query((event.target as any).value);
console.log(queryResult);
this.hotTable?.hotInstance?.render(); }
// define your custom callback function searchResultCounter( _instance: Handsontable, _row: number, _col: number, _value: Handsontable.CellValue, result: boolean ): void { _instance.getCellMeta(_row, _col).isSearchResult = result; if (result) { this.setResultCounter(this.resultCount + 1); } }
private setResultCounter(count: number): void { this.resultCount = count; }}/* 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 modulesregisterAllModules();
export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), { provide: HOT_GLOBAL_CONFIG, useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig, }, ],};/* end-file */<div> <example4-searching-values></example4-searching-values></div>Result
After following these steps, typing in your search input highlights matching cells with the htSearchResult CSS class. The query() call returns an array of matching { row, col, data } objects that you can use to build a results counter or navigate between matches.
Related API
How query() works
Calling query(queryStr, [callback], [queryMethod]) does two things:
- Iterates over every cell in the grid and tests each one using the
queryMethod. - After each test, calls the
callbackto update cell metadata (isSearchResult).
It returns an array of result objects - one for each matching cell:
| Property | Type | Description |
|---|---|---|
row | number | Visual row index of the matching cell |
col | number | Visual column index of the matching cell |
data | string|number|null | Value of the matching cell |
After calling query(), call hot.render() to refresh visual highlighting.
Search result class
After query() runs, every cell where isSearchResult === true automatically receives the CSS class htSearchResult. You can replace this class in two ways:
- At initialization: set
search: { searchResultClass: 'my-class' } - Programmatically: call
hot.getPlugin('search').setSearchResultClass('my-class')
queryMethod signature
The queryMethod function determines whether the query string matches a cell value. It is called once per cell during every query() call.
function queryMethod(query, value, cellProperties) { // return true for a match, false otherwise}| Parameter | Type | Description |
|---|---|---|
query | string | The search string passed to query() |
value | string|number|null | The cell value (from getDataAtCell()) |
cellProperties | object | The cell’s metadata object (includes locale, type, and other cell options) |
The built-in default performs a case-insensitive, locale-aware substring match:
function defaultQueryMethod(query, value, cellProperties) { if (query === undefined || query === null || query.length === 0) { return false; } if (value === undefined || value === null) { return false; }
return value.toString().toLocaleLowerCase(cellProperties.locale) .indexOf(query.toLocaleLowerCase(cellProperties.locale)) !== -1;}You can set a custom query method in three ways:
- At initialization:
search: { queryMethod: myQueryMethod } - Programmatically:
hot.getPlugin('search').setQueryMethod(myQueryMethod) - Per
query()call:searchPlugin.query(queryStr, callback, myQueryMethod)(applies to that call only)
callback signature
The callback function is called for every cell during a query() run, whether or not the cell matches. It is responsible for updating cell metadata so the renderer knows which cells to highlight.
function callback(instance, row, col, data, testResult) { // update cell metadata based on testResult}| Parameter | Type | Description |
|---|---|---|
instance | Handsontable | The Handsontable instance |
row | number | Visual row index |
col | number | Visual column index |
data | string|number|null | The cell value |
testResult | boolean | true if the cell matches the query, false otherwise |
The built-in default sets the isSearchResult flag on each cell’s metadata:
function defaultCallback(instance, row, col, data, testResult) { instance.getCellMeta(row, col).isSearchResult = testResult;}If you override the callback to add custom logic (for example, to count results), call the default behavior manually so that cell highlighting still works.
You can set a custom callback in three ways:
- At initialization:
search: { callback: myCallback } - Programmatically:
hot.getPlugin('search').setCallback(myCallback) - Per
query()call:searchPlugin.query(queryStr, myCallback)(applies to that call only)
Per-cell queryMethod and callback
Both queryMethod and callback can be overridden for individual cells, columns, or rows using Handsontable’s cascading configuration model. Set a search object directly in a cell, columns, or rows entry:
handsontable({ data: myData, search: true, columns: [ {}, // Column 1: exact match only, everything else uses the global queryMethod { search: { queryMethod(queryStr, value) { return queryStr.toString() === value.toString(); } } } ]});You can also set it programmatically on a specific cell using setCellMeta():
hot.setCellMeta(row, col, 'search', { queryMethod(queryStr, value) { return queryStr.toString() === value.toString(); }});Per-cell settings take precedence over the plugin-level queryMethod and callback. Only queryMethod and callback support per-cell overrides - searchResultClass does not.
Plugin configuration options
Configuration options
Plugins