Multi-column filter panel
In this tutorial, you will build an external filter panel with a category dropdown and a price range slider that controls Handsontable filtering. You will learn how to apply multiple conditions at once through the Filters plugin API and clear them all with a single button.
/* file: app.component.ts */import { Component, ViewChild } from '@angular/core';import { GridSettings, HotTableComponent, HotTableModule } from '@handsontable/angular-wrapper';import { RowObject } from 'handsontable/common';
type Product = { name: string; category: string; price: number; stock: number;};
const sourceData: Product[] = [ { name: 'Trail Bike', category: 'Bikes', price: 1499, stock: 12 }, { name: 'Road Helmet', category: 'Safety', price: 89, stock: 42 }, { name: 'Flat Pedals', category: 'Components', price: 59, stock: 80 }, { name: 'Hydration Pack', category: 'Accessories', price: 129, stock: 23 }, { name: 'Brake Pads', category: 'Components', price: 25, stock: 150 }, { name: 'Cycling Glasses', category: 'Accessories', price: 79, stock: 33 }, { name: 'Chain Lube', category: 'Maintenance', price: 16, stock: 99 }, { name: 'Torque Wrench', category: 'Maintenance', price: 139, stock: 14 }, { name: 'Kids Helmet', category: 'Safety', price: 54, stock: 20 }, { name: 'Gravel Bike', category: 'Bikes', price: 2199, stock: 7 },];
@Component({ standalone: true, imports: [HotTableModule], selector: 'example1-multi-column-filter-panel', template: ` <div class="example-controls-container"> <div class="filter-panel"> <label class="filter-label filter-label--wide"> Product name <input type="text" [value]="enteredName" placeholder="Contains..." (input)="onNameFilter($event)" /> </label> <label class="filter-label filter-label--wide"> Category <select [value]="selectedCategory" (change)="onCategoryFilter($event)"> <option value="">All categories</option> <option value="Bikes">Bikes</option> <option value="Safety">Safety</option> <option value="Components">Components</option> <option value="Accessories">Accessories</option> <option value="Maintenance">Maintenance</option> </select> </label> <label class="filter-label"> Min price <input type="number" min="0" [value]="minPrice" placeholder="0" (input)="onMinPriceFilter($event)" /> </label> <label class="filter-label"> Max price <input type="number" min="0" [value]="maxPrice" placeholder="2500" (input)="onMaxPriceFilter($event)" /> </label> <button type="button" (click)="clearFilters()">Clear all filters</button> </div> </div> <hot-table [data]="data" [settings]="gridSettings"></hot-table> `,})export class AppComponent { @ViewChild(HotTableComponent, { static: false }) readonly hotTable!: HotTableComponent;
readonly data = sourceData;
readonly gridSettings: GridSettings = { columns: [ { data: 'name', type: 'text', title: 'Product' }, { data: 'category', type: 'text', title: 'Category' }, { data: 'price', type: 'numeric', title: 'Price' }, { data: 'stock', type: 'numeric', title: 'Stock' }, ], colHeaders: ['Product', 'Category', 'Price', 'Stock'], rowHeaders: true, filters: true, dropdownMenu: false, width: '100%', height: 320, autoWrapRow: true, autoWrapCol: true, };
enteredName = ''; selectedCategory = ''; minPrice = ''; maxPrice = '';
onNameFilter(event: Event): void { this.enteredName = (event.target as HTMLInputElement).value.trim(); this.applyFilters(); }
onCategoryFilter(event: Event): void { this.selectedCategory = (event.target as HTMLSelectElement).value; this.applyFilters(); }
onMinPriceFilter(event: Event): void { this.minPrice = (event.target as HTMLInputElement).value.trim(); this.applyFilters(); }
onMaxPriceFilter(event: Event): void { this.maxPrice = (event.target as HTMLInputElement).value.trim(); this.applyFilters(); }
clearFilters(): void { this.enteredName = ''; this.selectedCategory = ''; this.minPrice = ''; this.maxPrice = '';
const hot = this.hotTable.hotInstance;
if (!hot) { return; }
const filtersPlugin = hot.getPlugin('filters');
filtersPlugin.clearConditions(); filtersPlugin.filter(); hot.render(); }
private applyFilters(): void { const hot = this.hotTable.hotInstance;
if (!hot) { return; }
const filtersPlugin = hot.getPlugin('filters');
filtersPlugin.clearConditions();
if (this.enteredName) { filtersPlugin.addCondition(0, 'contains', [this.enteredName]); }
if (this.selectedCategory) { filtersPlugin.addCondition(1, 'eq', [this.selectedCategory]); }
if (this.minPrice && this.maxPrice) { const lowerBound = Number(this.minPrice); const upperBound = Number(this.maxPrice);
if (Number.isFinite(lowerBound) && Number.isFinite(upperBound)) { filtersPlugin.addCondition(2, 'between', [lowerBound, upperBound]); } } else if (this.minPrice) { const lowerBound = Number(this.minPrice);
if (Number.isFinite(lowerBound)) { filtersPlugin.addCondition(2, 'gte', [lowerBound]); } } else if (this.maxPrice) { const upperBound = Number(this.maxPrice);
if (Number.isFinite(upperBound)) { filtersPlugin.addCondition(2, 'lte', [upperBound]); } }
filtersPlugin.filter(); 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';
registerAllModules();
export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), { provide: HOT_GLOBAL_CONFIG, useValue: { license: NON_COMMERCIAL_LICENSE } as HotGlobalConfig, }, ],};/* end-file */<div><example1-multi-column-filter-panel></example1-multi-column-filter-panel></div>Overview
This recipe shows how to control the Filters plugin from a filter panel outside of the grid. The panel includes controls aligned with the grid columns, and it applies all active filters together with AND logic.
Difficulty: Intermediate Time: ~20 minutes Libraries: Handsontable only
What You’ll Build
An external filter panel that:
- Enables
filters: truewhile hiding the built-in filter menu UI. - Uses
hot.getPlugin('filters')to control filtering through API calls. - Applies a text filter with
addCondition(columnIndex, 'contains', [value]). - Applies a numeric range filter with
addCondition(columnIndex, 'between', [min, max]). - Clears and re-applies all active conditions every time controls change.
- Includes a Clear all filters button that restores all rows.
Step 1 - Enable filtering and create the grid
Enable the Filters plugin in Handsontable configuration:
const hot = new Handsontable(container, { data: productData, colHeaders: ['Product', 'Category', 'Price', 'Stock'], columns: [ { data: 'name' }, { data: 'category' }, { data: 'price', type: 'numeric', numericFormat: { pattern: '$0,0.00', culture: 'en-US' } }, { data: 'stock', type: 'numeric' }, ], filters: true, dropdownMenu: false, licenseKey: 'non-commercial-and-evaluation',});Setting dropdownMenu: false keeps the filter panel external, while the Filters plugin remains active.
Step 2 - Get the plugin and apply conditions
Get the plugin instance and apply conditions every time a filter value changes:
const filtersPlugin = hot.getPlugin('filters');
const applyFilters = () => { filtersPlugin.clearConditions();
if (enteredName) { filtersPlugin.addCondition(0, 'contains', [enteredName]); }
if (selectedCategory) { filtersPlugin.addCondition(1, 'eq', [selectedCategory]); }
if (minPrice && maxPrice) { filtersPlugin.addCondition(2, 'between', [Number(minPrice), Number(maxPrice)]); } else if (minPrice) { filtersPlugin.addCondition(2, 'gte', [Number(minPrice)]); } else if (maxPrice) { filtersPlugin.addCondition(2, 'lte', [Number(maxPrice)]); }
filtersPlugin.filter(); hot.render();};This pattern guarantees each update uses the current set of active controls.
Step 3 - Add clear-all behavior
Add a button that clears control values, removes all conditions, and shows all rows again:
clearAllButton.addEventListener('click', () => { nameInput.value = ''; categorySelect.value = ''; minPriceInput.value = ''; maxPriceInput.value = ''; filtersPlugin.clearConditions(); filtersPlugin.filter(); hot.render();});How it works
- User changes one or more controls in the external panel.
- The code clears previously applied conditions.
- The code re-applies current conditions for product name, category, and price.
filtersPlugin.filter()updates the visible rows.- Clear all filters resets controls and restores the full dataset.
The full implementation is available in the runnable example above.
What you learned
- How to use
filtersPlugin.clearConditions()andfiltersPlugin.addCondition()to apply fresh conditions on every control change. - How to call
filtersPlugin.filter()to update the visible rows after adding conditions, and howhot.render()keeps the view in sync. - How to build a clear-all button that resets external controls, removes all conditions, and restores the full dataset.
- Why you must enable the
Filtersplugin withfilters: trueand pair it withdropdownMenu: trueto expose per-column filter UI alongside your external panel.
Next steps
- Explore external search box to add a text search that works alongside the filter panel.
- Read the Filters plugin API reference for the full list of built-in condition types (between, contains, begins with, and more).