A powerful, reusable Angular column filter component with support for multiple field types, advanced filtering rules, and customizable match modes.
- π― Live Demo - See it in action!
- π¦ NPM Package
- π GitHub Repository
New to this library? Start here:
- π Getting Started Tutorial β - Complete step-by-step guide: How to install, import, and use
- Installation steps
- TypeScript imports explained
- Basic and advanced examples
- All field types with code samples
- Data filtering implementation
- Troubleshooting guide
More Resources:
- π Complete Documentation - Full API reference, all features explained
- π‘ Usage Examples - Advanced usage patterns and programmatic control
- π Deployment Guide - How to deploy your Angular app
- π Report Bug
- π¬ Request Feature
- β Multiple Filter Rules: Add multiple filter conditions per column
- β
Multiple Field Types: Specialized filters for different data types
- Text - Text fields (default)
- Currency - Currency values with symbol support
- Age/Number - Numeric values
- Date - Date values with date picker
- Status - Predefined status options dropdown
- β
Global Match Mode: Choose how to combine multiple rules
- Match All Rules (AND Logic): All rules must match
- Match Any Rule (OR Logic): Any rule can match (default)
- β Various Match Types: Different matching options based on field type
- β Visual Feedback: Blinking red filter icon with X mark when active - clearly indicates applied filters
- β Backend Mode: Send filter payloads directly to your backend API instead of filtering locally
- β
Single/Multiple Rules: Control whether users can add multiple filter rules with
allowMultipleRulesoption - β Single Filter Open: Only one filter dropdown can be open at a time
- β ESC Key Support: Press ESC to close the open filter
- β
Programmatic Control: Clear filters programmatically using
clearFilter()method - β Type-Safe: Fully typed with TypeScript
- β Standalone Component: Works with Angular 14+ standalone components
- β Fully Customizable: Configurable inputs for customization
- β Accessible: ARIA labels and keyboard navigation support
- β
Data Adaptive: Simply update
columnKeyandcolumnNamewhen your data changes
npm install ngx-column-filter-popupNote: This package is published with TypeScript source files. Make sure your Angular project has TypeScript configured to compile these files.
π‘ New to this library? Check out the Getting Started Tutorial for a complete step-by-step guide with examples!
import { Component } from '@angular/core';
import { ColumnFilterComponent } from 'ngx-column-filter-popup';
import { FilterConfig, applyColumnFilter } from 'ngx-column-filter-popup';
@Component({
selector: 'app-example',
imports: [ColumnFilterComponent],
template: `
<lib-column-filter
columnName="first name"
columnKey="firstName"
(filterApplied)="onFilterApplied($event)"
(filterCleared)="onFilterCleared()">
</lib-column-filter>
`
})
export class ExampleComponent {
onFilterApplied(filterConfig: FilterConfig) {
console.log('Filter applied:', filterConfig);
// Apply filter to your data
}
onFilterCleared() {
console.log('Filter cleared');
// Clear filter from your data
}
}import { Component, ViewChildren, QueryList } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ColumnFilterComponent } from 'ngx-column-filter-popup';
import { FilterConfig, applyColumnFilter } from 'ngx-column-filter-popup';
interface User {
id: number;
firstName: string;
lastName: string;
email: string;
}
@Component({
selector: 'app-example',
imports: [CommonModule, ColumnFilterComponent],
template: `
<table>
<thead>
<tr>
<th>
First Name
<lib-column-filter
columnName="first name"
columnKey="firstName"
[allowMultipleRules]="false"
[backendMode]="isBackendMode('firstName')"
(filterApplied)="onFilterApplied('firstName', $event)"
(filterCleared)="onFilterCleared('firstName')">
</lib-column-filter>
</th>
<th>
Last Name
<lib-column-filter
columnName="last name"
columnKey="lastName"
[allowMultipleRules]="true"
[backendMode]="isBackendMode('lastName')"
(filterApplied)="onFilterApplied('lastName', $event)"
(filterCleared)="onFilterCleared('lastName')">
</lib-column-filter>
</th>
<th>
Email
<lib-column-filter
columnName="email"
columnKey="email"
[backendMode]="isBackendMode('email')"
(filterApplied)="onFilterApplied('email', $event)"
(filterCleared)="onFilterCleared('email')">
</lib-column-filter>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of filteredUsers">
<td>{{ user.firstName }}</td>
<td>{{ user.lastName }}</td>
<td>{{ user.email }}</td>
</tr>
</tbody>
</table>
<button (click)="clearAllFilters()">Clear All Filters</button>
`
})
export class ExampleComponent {
originalData: User[] = [
{ id: 1, firstName: 'John', lastName: 'Doe', email: 'john@example.com' }
];
filteredData: User[] = [...this.originalData];
// β
Unified filter storage - single source of truth
filters = new Map<string, FilterConfig | null>();
// β
Configuration: Which columns use backend mode
readonly backendModeColumns = new Set<string>(['firstName', 'email']);
@ViewChildren(ColumnFilterComponent) filterComponents!: QueryList<ColumnFilterComponent>;
// β
Generic filter handler - works for all columns
onFilterApplied(columnKey: string, filterConfig: FilterConfig) {
this.filters.set(columnKey, filterConfig);
if (this.isBackendMode(columnKey)) {
this.sendAllBackendFiltersToBackend();
}
this.applyAllFilters();
}
// β
Generic filter clear handler
onFilterCleared(columnKey: string) {
this.filters.set(columnKey, null);
if (this.isBackendMode(columnKey)) {
this.sendAllBackendFiltersToBackend();
}
this.applyAllFilters();
}
// β
Check if column uses backend mode
isBackendMode(columnKey: string): boolean {
return this.backendModeColumns.has(columnKey);
}
// β
Apply all filters - automatically skips backend mode columns
private applyAllFilters() {
let result = [...this.originalData];
this.filters.forEach((filterConfig, columnKey) => {
// Skip backend mode columns (handled by backend)
if (filterConfig && !this.isBackendMode(columnKey)) {
result = applyColumnFilter(result, columnKey, filterConfig);
}
});
this.filteredData = result;
}
// β
Clear all filters programmatically
clearAllFilters() {
this.filters.clear();
this.sendAllBackendFiltersToBackend();
this.filteredData = [...this.originalData];
// Clear UI state in all filter components (icons/inputs)
if (this.filterComponents) {
this.filterComponents.forEach((filter: ColumnFilterComponent) => {
filter.clearFilter();
});
}
}
// β
Send all backend filters to API
private sendAllBackendFiltersToBackend() {
const activeFilters: Array<{
field: string;
matchType: string;
value: string;
fieldType: string;
}> = [];
this.backendModeColumns.forEach(columnKey => {
const filterConfig = this.filters.get(columnKey);
if (filterConfig && filterConfig.rules.length > 0) {
filterConfig.rules.forEach(rule => {
if (rule.value && rule.value.trim() !== '') {
activeFilters.push({
field: columnKey,
matchType: rule.matchType,
value: rule.value.trim(),
fieldType: filterConfig.fieldType || 'text'
});
}
});
}
});
const payload = { activeFilters, count: activeFilters.length };
// Send to your backend API
console.log('Backend payload:', payload);
}
}import { NgModule } from '@angular/core';
import { ColumnFilterModule } from 'ngx-column-filter-popup';
@NgModule({
imports: [ColumnFilterModule],
// ...
})
export class YourModule {}<lib-column-filter
columnName="name"
columnKey="name"
fieldType="text"
(filterApplied)="onFilterApplied('name', $event)"
(filterCleared)="onFilterCleared('name')">
</lib-column-filter><lib-column-filter
columnName="balance"
columnKey="balance"
fieldType="currency"
currencySymbol="$"
(filterApplied)="onFilterApplied('balance', $event)"
(filterCleared)="onFilterCleared('balance')">
</lib-column-filter><lib-column-filter
columnName="age"
columnKey="age"
fieldType="age"
(filterApplied)="onFilterApplied('age', $event)"
(filterCleared)="onFilterCleared('age')">
</lib-column-filter><lib-column-filter
columnName="date"
columnKey="date"
fieldType="date"
(filterApplied)="onFilterApplied('date', $event)"
(filterCleared)="onFilterCleared('date')">
</lib-column-filter><lib-column-filter
columnName="status"
columnKey="status"
fieldType="status"
[statusOptions]="['qualified', 'unqualified', 'negotiation', 'new']"
(filterApplied)="onFilterApplied('status', $event)"
(filterCleared)="onFilterCleared('status')">
</lib-column-filter>π‘ Note: All examples use generic handlers
onFilterApplied(columnKey, $event)andonFilterCleared(columnKey)- no need for separate functions per filter!
| Input | Type | Default | Description |
|---|---|---|---|
columnName |
string |
'' |
Display name of the column (used in placeholder) |
columnKey |
string |
'' |
Property name to filter on |
fieldType |
FieldType |
'text' |
Field type: 'text', 'currency', 'age', 'date', or 'status' |
currencySymbol |
string |
'$' |
Currency symbol for currency field type (optional) |
statusOptions |
string[] |
[] |
Array of status options for status field type (required for status) |
initialFilter |
FilterConfig? |
undefined |
Initial filter configuration (optional) |
placeholder |
string? |
undefined |
Custom placeholder text. Default: "Search by {columnName}" |
availableMatchTypes |
MatchType[]? |
undefined |
Customize available match types (optional) |
backendMode |
boolean |
false |
When true, component emits filter data for backend API instead of frontend filtering |
allowMultipleRules |
boolean |
true |
When false, hides Add/Remove Rule buttons (single rule only) |
| Output | Type | Description |
|---|---|---|
filterApplied |
EventEmitter<FilterConfig> |
Emitted when filter is applied |
filterCleared |
EventEmitter<void> |
Emitted when filter is cleared |
| Method | Description |
|---|---|
clearFilter() |
Programmatically clear all filter rules and reset the filter |
type FieldType = 'text' | 'currency' | 'age' | 'date' | 'status';
interface FilterConfig {
rules: FilterRule[];
globalMatchMode?: GlobalMatchMode; // 'match-all-rules' | 'match-any-rule'
fieldType?: FieldType;
statusOptions?: string[];
}
interface FilterRule {
id: string;
matchType: MatchType;
value: string;
}Apply filter rules to a dataset:
import { applyColumnFilter } from 'ngx-column-filter-popup';
const filteredData = applyColumnFilter(
data,
'columnKey',
filterConfig
);Check if a single item matches filter rules:
import { itemMatchesFilter } from 'ngx-column-filter-popup';
const matches = itemMatchesFilter(
item,
'columnKey',
filterConfig
);When backendMode is enabled, the component collects filter data and emits it in a format ready for your backend API. No frontend filtering is applied.
import { Component } from '@angular/core';
import { ColumnFilterComponent } from 'ngx-column-filter-popup';
import { FilterConfig } from 'ngx-column-filter-popup';
@Component({
selector: 'app-example',
imports: [ColumnFilterComponent],
template: `
<lib-column-filter
columnName="first name"
columnKey="firstName"
[backendMode]="true"
(filterApplied)="onFilterApplied($event)"
(filterCleared)="onFilterCleared()">
</lib-column-filter>
`
})
export class ExampleComponent {
filters = new Map<string, FilterConfig | null>();
readonly backendModeColumns = new Set<string>(['firstName', 'email']);
onFilterApplied(columnKey: string, filterConfig: FilterConfig) {
this.filters.set(columnKey, filterConfig);
this.sendToBackend();
}
onFilterCleared(columnKey: string) {
this.filters.set(columnKey, null);
this.sendToBackend();
}
private sendToBackend() {
const activeFilters: Array<{
field: string;
matchType: string;
value: string;
fieldType: string;
}> = [];
this.backendModeColumns.forEach(columnKey => {
const filterConfig = this.filters.get(columnKey);
if (filterConfig && filterConfig.rules.length > 0) {
filterConfig.rules.forEach(rule => {
if (rule.value && rule.value.trim() !== '') {
activeFilters.push({
field: columnKey,
matchType: rule.matchType,
value: rule.value.trim(),
fieldType: filterConfig.fieldType || 'text'
});
}
});
}
});
const payload = {
activeFilters: activeFilters,
count: activeFilters.length
};
// Send to your backend API
// this.httpClient.post('/api/filters', payload).subscribe(...);
console.log('Backend payload:', payload);
}
}{
"activeFilters": [
{
"field": "firstName",
"matchType": "contains",
"value": "John",
"fieldType": "text"
},
{
"field": "email",
"matchType": "contains",
"value": "example",
"fieldType": "text"
}
],
"count": 2
}Control whether users can add multiple filter rules:
<!-- Multiple rules allowed (default) -->
<lib-column-filter
columnName="name"
columnKey="name"
[allowMultipleRules]="true">
</lib-column-filter>
<!-- Single rule only (Add/Remove buttons hidden) -->
<lib-column-filter
columnName="email"
columnKey="email"
[allowMultipleRules]="false">
</lib-column-filter>When allowMultipleRules="false":
- β Add Rule button is hidden
- β Remove Rule buttons are hidden
- β Global Match Mode toggle is hidden
- β Users can only use a single filter rule
When allowMultipleRules="true" (default):
- β All features work normally
- β Users can add multiple rules
- β Match All/Match Any toggle is available
import { Component, ViewChild } from '@angular/core';
import { ColumnFilterComponent } from 'ngx-column-filter-popup';
@Component({
template: `
<lib-column-filter
#nameFilter
columnName="name"
columnKey="name">
</lib-column-filter>
<button (click)="clearFilter()">Clear Filter</button>
`
})
export class ExampleComponent {
@ViewChild('nameFilter') filter!: ColumnFilterComponent;
clearFilter() {
this.filter.clearFilter(); // Programmatically clear the filter
}
}β Modern Implementation using Generic Handlers (Recommended):
import { Component, ViewChildren, QueryList } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ColumnFilterComponent } from 'ngx-column-filter-popup';
import { FilterConfig, applyColumnFilter } from 'ngx-column-filter-popup';
interface User {
id: number;
firstName: string;
lastName: string;
email: string;
age: number;
balance: number;
joinDate: string;
status: string;
}
@Component({
selector: 'app-user-list',
imports: [CommonModule, ColumnFilterComponent],
template: `
<button (click)="clearAllFilters()">Clear All Filters</button>
<table>
<thead>
<tr>
<th>
First Name
<lib-column-filter
columnName="first name"
columnKey="firstName"
[allowMultipleRules]="false"
[backendMode]="isBackendMode('firstName')"
(filterApplied)="onFilterApplied('firstName', $event)"
(filterCleared)="onFilterCleared('firstName')">
</lib-column-filter>
</th>
<th>
Last Name
<lib-column-filter
columnName="last name"
columnKey="lastName"
(filterApplied)="onFilterApplied('lastName', $event)"
(filterCleared)="onFilterCleared('lastName')">
</lib-column-filter>
</th>
<th>
Email
<lib-column-filter
columnName="email"
columnKey="email"
[backendMode]="isBackendMode('email')"
(filterApplied)="onFilterApplied('email', $event)"
(filterCleared)="onFilterCleared('email')">
</lib-column-filter>
</th>
<th>
Age
<lib-column-filter
columnName="age"
columnKey="age"
fieldType="age"
(filterApplied)="onFilterApplied('age', $event)"
(filterCleared)="onFilterCleared('age')">
</lib-column-filter>
</th>
<th>
Balance
<lib-column-filter
columnName="balance"
columnKey="balance"
fieldType="currency"
currencySymbol="$"
(filterApplied)="onFilterApplied('balance', $event)"
(filterCleared)="onFilterCleared('balance')">
</lib-column-filter>
</th>
<th>
Join Date
<lib-column-filter
columnName="join date"
columnKey="joinDate"
fieldType="date"
(filterApplied)="onFilterApplied('joinDate', $event)"
(filterCleared)="onFilterCleared('joinDate')">
</lib-column-filter>
</th>
<th>
Status
<lib-column-filter
columnName="status"
columnKey="status"
fieldType="status"
[statusOptions]="statusOptions"
(filterApplied)="onFilterApplied('status', $event)"
(filterCleared)="onFilterCleared('status')">
</lib-column-filter>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of filteredUsers">
<td>{{ user.firstName }}</td>
<td>{{ user.lastName }}</td>
<td>{{ user.email }}</td>
<td>{{ user.age }}</td>
<td>${{ user.balance }}</td>
<td>{{ user.joinDate }}</td>
<td>{{ user.status }}</td>
</tr>
</tbody>
</table>
`
})
export class UserListComponent {
users: User[] = [
{ id: 1, firstName: 'John', lastName: 'Doe', email: 'john@example.com', age: 30, balance: 50000, joinDate: '2020-01-15', status: 'active' }
];
filteredUsers: User[] = [...this.users];
statusOptions = ['active', 'inactive', 'on-leave'];
// β
Unified filter storage - single source of truth
filters = new Map<string, FilterConfig | null>();
// β
Configuration: Which columns use backend mode
readonly backendModeColumns = new Set<string>(['firstName', 'email']);
@ViewChildren(ColumnFilterComponent) filterComponents!: QueryList<ColumnFilterComponent>;
// β
Generic filter handler - works for ALL columns (no separate functions needed!)
onFilterApplied(columnKey: string, filterConfig: FilterConfig): void {
this.filters.set(columnKey, filterConfig);
if (this.isBackendMode(columnKey)) {
this.sendAllBackendFiltersToBackend();
}
this.applyAllFilters();
}
// β
Generic filter clear handler - works for ALL columns
onFilterCleared(columnKey: string): void {
this.filters.set(columnKey, null);
if (this.isBackendMode(columnKey)) {
this.sendAllBackendFiltersToBackend();
}
this.applyAllFilters();
}
// β
Check if column uses backend mode
isBackendMode(columnKey: string): boolean {
return this.backendModeColumns.has(columnKey);
}
// β
Apply all filters - automatically skips backend mode columns
private applyAllFilters(): void {
let result = [...this.users];
this.filters.forEach((filterConfig, columnKey) => {
// Skip backend mode columns (handled by backend)
if (filterConfig && !this.isBackendMode(columnKey)) {
result = applyColumnFilter(result, columnKey, filterConfig);
}
});
this.filteredUsers = result;
}
// β
Clear all filters programmatically
clearAllFilters(): void {
this.filters.clear();
this.sendAllBackendFiltersToBackend();
this.filteredUsers = [...this.users];
// Clear UI state in all filter components (icons/inputs)
if (this.filterComponents) {
this.filterComponents.forEach((filter: ColumnFilterComponent) => {
filter.clearFilter();
});
}
}
// β
Send all backend filters to API
private sendAllBackendFiltersToBackend(): void {
const activeFilters: Array<{
field: string;
matchType: string;
value: string;
fieldType: string;
}> = [];
this.backendModeColumns.forEach(columnKey => {
const filterConfig = this.filters.get(columnKey);
if (filterConfig && filterConfig.rules.length > 0) {
filterConfig.rules.forEach(rule => {
if (rule.value && rule.value.trim() !== '') {
activeFilters.push({
field: columnKey,
matchType: rule.matchType,
value: rule.value.trim(),
fieldType: filterConfig.fieldType || 'text'
});
}
});
}
});
const payload = { activeFilters, count: activeFilters.length };
// Send to your backend API
console.log('Backend payload:', payload);
}
}β¨ Key Benefits of This Approach:
- β
No separate functions per filter - One
onFilterApplied()handles all columns - β Easy to add new filters - Just add HTML, no new functions needed
- β Clean and maintainable - Map-based storage, generic handlers
- β Backend mode support - Configurable per column
- β Single source of truth - All filters in one Map
- Getting Started Tutorial - Step-by-step guide with examples
- What to import in TypeScript
- How to setup component
- Complete working examples
- Common patterns
-
Complete Documentation - Full API reference
- All inputs and outputs
- Utility functions
- Type definitions
- Match modes explained
- Data structure adaptation
-
Usage Examples - Advanced patterns
- Programmatic filter control
- Multiple filters management
- Custom configurations
-
Deployment Guide - Deploy your Angular app
- GitHub Pages
- Vercel
- Netlify
- Firebase Hosting
The component uses SCSS and includes default styles. You can customize the appearance by overriding CSS classes:
.column-filter-wrapper- Main wrapper.filter-trigger- Filter button.filter-dropdown- Dropdown container.filter-rule- Individual filter rule.btn-apply- Apply button.btn-clear- Clear button
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
- Angular 14+
- TypeScript 4.7+
MIT
Made with β€οΈ by Shivam Sharma
Contributions are welcome! Please feel free to submit a Pull Request.