Searching values
Searching values
Search data across Handsontable using the built-in API methods of the Search plugin, and implement your own search UI.
Overview
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.
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')
Custom queryMethod
The queryMethod function determines whether the query string matches a cell value. It is called once per cell during every query() call.
Signature:
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)
Custom result callback
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.
Signature:
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.
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 } from '@handsontable/angular-wrapper';
@Component({ selector: 'example1-searching-values', standalone: false, 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 Example1SearchingValuesComponent { @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.module.ts */import { NgModule, ApplicationConfig } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { registerAllModules } from 'handsontable/registry';import { HOT_GLOBAL_CONFIG, HotGlobalConfig, HotTableModule } from '@handsontable/angular-wrapper';import { CommonModule } from '@angular/common';import { NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';/* start:skip-in-compilation */import { Example1SearchingValuesComponent } from './app.component';/* end:skip-in-compilation */
// register Handsontable's modulesregisterAllModules();
export const appConfig: ApplicationConfig = { providers: [ { provide: HOT_GLOBAL_CONFIG, useValue: { license: NON_COMMERCIAL_LICENSE, } as HotGlobalConfig } ],};
@NgModule({ imports: [ BrowserModule, HotTableModule, CommonModule ], declarations: [ Example1SearchingValuesComponent ], providers: [...appConfig.providers], bootstrap: [ Example1SearchingValuesComponent ]})
export class AppModule { }/* 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 } from '@handsontable/angular-wrapper';
@Component({ selector: 'example2-searching-values', standalone: false, 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 Example2SearchingValuesComponent { @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.module.ts */import { NgModule, ApplicationConfig } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { registerAllModules } from 'handsontable/registry';import { HOT_GLOBAL_CONFIG, HotGlobalConfig, HotTableModule } from '@handsontable/angular-wrapper';import { CommonModule } from '@angular/common';import { NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';/* start:skip-in-compilation */import { Example2SearchingValuesComponent } from './app.component';/* end:skip-in-compilation */
// register Handsontable's modulesregisterAllModules();
export const appConfig: ApplicationConfig = { providers: [ { provide: HOT_GLOBAL_CONFIG, useValue: { license: NON_COMMERCIAL_LICENSE, } as HotGlobalConfig } ],};
@NgModule({ imports: [ BrowserModule, HotTableModule, CommonModule ], declarations: [ Example2SearchingValuesComponent ], providers: [...appConfig.providers], bootstrap: [ Example2SearchingValuesComponent ]})
export class AppModule { }/* 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 } from '@handsontable/angular-wrapper';
@Component({ selector: 'example3-searching-values', standalone: false, 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 Example3SearchingValuesComponent { @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.module.ts */import { NgModule, ApplicationConfig } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { registerAllModules } from 'handsontable/registry';import { HOT_GLOBAL_CONFIG, HotGlobalConfig, HotTableModule } from '@handsontable/angular-wrapper';import { CommonModule } from '@angular/common';import { NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';/* start:skip-in-compilation */import { Example3SearchingValuesComponent } from './app.component';/* end:skip-in-compilation */
// register Handsontable's modulesregisterAllModules();
export const appConfig: ApplicationConfig = { providers: [ { provide: HOT_GLOBAL_CONFIG, useValue: { license: NON_COMMERCIAL_LICENSE, } as HotGlobalConfig } ],};
@NgModule({ imports: [ BrowserModule, HotTableModule, CommonModule ], declarations: [ Example3SearchingValuesComponent ], providers: [...appConfig.providers], bootstrap: [ Example3SearchingValuesComponent ]})
export class AppModule { }/* 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 } from '@handsontable/angular-wrapper';import Handsontable from 'handsontable/base';
@Component({ selector: 'example4-searching-values', standalone: false, 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 Example4SearchingValuesComponent { @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: any, result: any ) => 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: any, result: any ): 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.module.ts */import { NgModule, ApplicationConfig } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { registerAllModules } from 'handsontable/registry';import { HOT_GLOBAL_CONFIG, HotGlobalConfig, HotTableModule } from '@handsontable/angular-wrapper';import { CommonModule } from '@angular/common';import { NON_COMMERCIAL_LICENSE } from '@handsontable/angular-wrapper';/* start:skip-in-compilation */import { Example4SearchingValuesComponent } from './app.component';/* end:skip-in-compilation */
// register Handsontable's modulesregisterAllModules();
export const appConfig: ApplicationConfig = { providers: [ { provide: HOT_GLOBAL_CONFIG, useValue: { license: NON_COMMERCIAL_LICENSE, } as HotGlobalConfig } ],};
@NgModule({ imports: [ BrowserModule, HotTableModule, CommonModule ], declarations: [ Example4SearchingValuesComponent ], providers: [...appConfig.providers], bootstrap: [ Example4SearchingValuesComponent ]})
export class AppModule { }/* end-file */<div> <example4-searching-values></example4-searching-values></div>Related API reference
Configuration options
Plugins