Skip to content

kakarotx10/ngx-column-filter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Column Filter Library

A powerful, reusable Angular column filter component with support for multiple field types, advanced filtering rules, and customizable match modes.

npm version License: MIT GitHub stars GitHub forks

πŸš€ Quick Links

πŸ“š Documentation & Guides

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:

πŸ› Support

Features

  • βœ… 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 allowMultipleRules option
  • βœ… 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 columnKey and columnName when your data changes

Installation

npm install ngx-column-filter-popup

Note: 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!

Quick Start

Standalone Component (Angular 14+)

Basic Example:

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
  }
}

Complete Example with New Features (Backend Mode, allowMultipleRules):

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);
  }
}

Module-Based (Optional)

import { NgModule } from '@angular/core';
import { ColumnFilterModule } from 'ngx-column-filter-popup';

@NgModule({
  imports: [ColumnFilterModule],
  // ...
})
export class YourModule {}

Field Types

Text Field (Default)

<lib-column-filter
  columnName="name"
  columnKey="name"
  fieldType="text"
  (filterApplied)="onFilterApplied('name', $event)"
  (filterCleared)="onFilterCleared('name')">
</lib-column-filter>

Currency Field

<lib-column-filter
  columnName="balance"
  columnKey="balance"
  fieldType="currency"
  currencySymbol="$"
  (filterApplied)="onFilterApplied('balance', $event)"
  (filterCleared)="onFilterCleared('balance')">
</lib-column-filter>

Age/Number Field

<lib-column-filter
  columnName="age"
  columnKey="age"
  fieldType="age"
  (filterApplied)="onFilterApplied('age', $event)"
  (filterCleared)="onFilterCleared('age')">
</lib-column-filter>

Date Field

<lib-column-filter
  columnName="date"
  columnKey="date"
  fieldType="date"
  (filterApplied)="onFilterApplied('date', $event)"
  (filterCleared)="onFilterCleared('date')">
</lib-column-filter>

Status Field

<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) and onFilterCleared(columnKey) - no need for separate functions per filter!

API Reference

ColumnFilterComponent

Inputs

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)

Outputs

Output Type Description
filterApplied EventEmitter<FilterConfig> Emitted when filter is applied
filterCleared EventEmitter<void> Emitted when filter is cleared

Public Methods

Method Description
clearFilter() Programmatically clear all filter rules and reset the filter

FilterConfig Interface

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;
}

Utility Functions

applyColumnFilter

Apply filter rules to a dataset:

import { applyColumnFilter } from 'ngx-column-filter-popup';

const filteredData = applyColumnFilter(
  data,
  'columnKey',
  filterConfig
);

itemMatchesFilter

Check if a single item matches filter rules:

import { itemMatchesFilter } from 'ngx-column-filter-popup';

const matches = itemMatchesFilter(
  item,
  'columnKey',
  filterConfig
);

Backend Mode (Backend API Integration)

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.

Backend Mode Example:

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);
  }
}

Backend Payload Format:

{
  "activeFilters": [
    {
      "field": "firstName",
      "matchType": "contains",
      "value": "John",
      "fieldType": "text"
    },
    {
      "field": "email",
      "matchType": "contains",
      "value": "example",
      "fieldType": "text"
    }
  ],
  "count": 2
}

Single Rule Mode (allowMultipleRules)

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

Programmatic Control

Clearing Filters Programmatically

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
  }
}

Complete Example

βœ… 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

πŸ“– Documentation

For Beginners:

  • Getting Started Tutorial - Step-by-step guide with examples
    • What to import in TypeScript
    • How to setup component
    • Complete working examples
    • Common patterns

For Advanced Users:

  • 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

Styling

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

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

Requirements

  • Angular 14+
  • TypeScript 4.7+

License

MIT

Author

Made with ❀️ by Shivam Sharma

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

About

A powerful, reusable Angular column filter component with support for multiple field types, advanced filtering rules, and customizable match modes.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors