ngx-column-filter-popup 1.0.10 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -863
- package/fesm2022/ngx-column-filter-popup.mjs +779 -0
- package/fesm2022/ngx-column-filter-popup.mjs.map +1 -0
- package/package.json +21 -68
- package/types/ngx-column-filter-popup.d.ts +307 -0
- package/DOCUMENTATION.md +0 -865
- package/GETTING_STARTED.md +0 -767
- package/LICENSE +0 -21
- package/USAGE_EXAMPLES.md +0 -385
- package/src/app/components/column-filter/column-filter.component.html +0 -146
- package/src/app/components/column-filter/column-filter.component.scss +0 -451
- package/src/app/components/column-filter/column-filter.component.ts +0 -546
- package/src/app/components/column-filter/column-filter.module.ts +0 -31
- package/src/app/lib/models/filter.models.ts +0 -51
- package/src/app/lib/public-api.ts +0 -21
- package/src/app/lib/services/column-filter.service.ts +0 -35
- package/src/app/lib/utils/column-filter.utils.ts +0 -236
package/README.md
CHANGED
|
@@ -1,892 +1,63 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ColumnFilterPopup
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.0.0.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](https://github.com/kakarotx10/ngx-column-filter)
|
|
8
|
-
[](https://github.com/kakarotx10/ngx-column-filter)
|
|
5
|
+
## Code scaffolding
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- 🎯 [Live Demo](https://ngx-column-filter.netlify.app/) - See it in action!
|
|
13
|
-
- 📦 [NPM Package](https://www.npmjs.com/package/ngx-column-filter-popup)
|
|
14
|
-
- 🏠 [GitHub Repository](https://github.com/kakarotx10/ngx-column-filter)
|
|
15
|
-
|
|
16
|
-
## 📚 Documentation & Guides
|
|
17
|
-
|
|
18
|
-
**New to this library?** Start here:
|
|
19
|
-
|
|
20
|
-
- 📖 **[Getting Started Tutorial](./GETTING_STARTED.md)** ⭐ - **Complete step-by-step guide: How to install, import, and use**
|
|
21
|
-
- Installation steps
|
|
22
|
-
- TypeScript imports explained
|
|
23
|
-
- Basic and advanced examples
|
|
24
|
-
- All field types with code samples
|
|
25
|
-
- Data filtering implementation
|
|
26
|
-
- Troubleshooting guide
|
|
27
|
-
|
|
28
|
-
**More Resources:**
|
|
29
|
-
|
|
30
|
-
- 📘 [Complete Documentation](./DOCUMENTATION.md) - Full API reference, all features explained
|
|
31
|
-
- 💡 [Usage Examples](./USAGE_EXAMPLES.md) - Advanced usage patterns and programmatic control
|
|
32
|
-
- 🚀 [Deployment Guide](./DEPLOYMENT.md) - How to deploy your Angular app
|
|
33
|
-
|
|
34
|
-
## 🐛 Support
|
|
35
|
-
|
|
36
|
-
- 🐛 [Report Bug](https://github.com/kakarotx10/ngx-column-filter/issues)
|
|
37
|
-
- 💬 [Request Feature](https://github.com/kakarotx10/ngx-column-filter/issues)
|
|
38
|
-
|
|
39
|
-
## Features
|
|
40
|
-
|
|
41
|
-
- ✅ **Multiple Filter Rules**: Add multiple filter conditions per column
|
|
42
|
-
- ✅ **Multiple Field Types**: Specialized filters for different data types
|
|
43
|
-
- **Text** - Text fields (default)
|
|
44
|
-
- **Currency** - Currency values with symbol support
|
|
45
|
-
- **Age/Number** - Numeric values
|
|
46
|
-
- **Date** - Date values with date picker
|
|
47
|
-
- **Status** - Predefined status options dropdown
|
|
48
|
-
- ✅ **Global Match Mode**: Choose how to combine multiple rules
|
|
49
|
-
- **Match All Rules** (AND Logic): All rules must match
|
|
50
|
-
- **Match Any Rule** (OR Logic): Any rule can match (default)
|
|
51
|
-
- ✅ **Various Match Types**: Different matching options based on field type
|
|
52
|
-
- ✅ **Visual Feedback**: Blinking red filter icon with X mark when active - clearly indicates applied filters
|
|
53
|
-
- ✅ **Backend Mode**: Send filter payloads directly to your backend API instead of filtering locally
|
|
54
|
-
- ✅ **Single/Multiple Rules**: Control whether users can add multiple filter rules with `allowMultipleRules` option
|
|
55
|
-
- ✅ **Single Filter Open**: Only one filter dropdown can be open at a time
|
|
56
|
-
- ✅ **ESC Key Support**: Press ESC to close the open filter
|
|
57
|
-
- ✅ **Programmatic Control**: Clear filters programmatically using `clearFilter()` method
|
|
58
|
-
- ✅ **Type-Safe**: Fully typed with TypeScript
|
|
59
|
-
- ✅ **Standalone Component**: Works with Angular 14+ standalone components
|
|
60
|
-
- ✅ **Fully Customizable**: Configurable inputs for customization
|
|
61
|
-
- ✅ **Accessible**: ARIA labels and keyboard navigation support
|
|
62
|
-
- ✅ **Data Adaptive**: Simply update `columnKey` and `columnName` when your data changes
|
|
63
|
-
|
|
64
|
-
## Installation
|
|
7
|
+
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
|
|
65
8
|
|
|
66
9
|
```bash
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### TypeScript Configuration
|
|
71
|
-
|
|
72
|
-
This package is published with TypeScript source files. To avoid compilation errors, add this to your `tsconfig.json`:
|
|
73
|
-
|
|
74
|
-
```json
|
|
75
|
-
{
|
|
76
|
-
"compilerOptions": {
|
|
77
|
-
"skipLibCheck": true
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
**Or** if you're using Angular's default tsconfig, ensure `skipLibCheck` is enabled (it's usually enabled by default in Angular 14+).
|
|
83
|
-
|
|
84
|
-
> 💡 **New to this library?** Check out the **[Getting Started Tutorial](./GETTING_STARTED.md)** for a complete step-by-step guide with examples!
|
|
85
|
-
|
|
86
|
-
## Quick Start
|
|
87
|
-
|
|
88
|
-
### Standalone Component (Angular 14+)
|
|
89
|
-
|
|
90
|
-
#### Basic Example:
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
import { Component } from '@angular/core';
|
|
94
|
-
import { ColumnFilterComponent } from 'ngx-column-filter-popup';
|
|
95
|
-
import { FilterConfig, applyColumnFilter } from 'ngx-column-filter-popup';
|
|
96
|
-
|
|
97
|
-
@Component({
|
|
98
|
-
selector: 'app-example',
|
|
99
|
-
imports: [ColumnFilterComponent],
|
|
100
|
-
template: `
|
|
101
|
-
<lib-column-filter
|
|
102
|
-
columnName="first name"
|
|
103
|
-
columnKey="firstName"
|
|
104
|
-
(filterApplied)="onFilterApplied($event)"
|
|
105
|
-
(filterCleared)="onFilterCleared()">
|
|
106
|
-
</lib-column-filter>
|
|
107
|
-
`
|
|
108
|
-
})
|
|
109
|
-
export class ExampleComponent {
|
|
110
|
-
onFilterApplied(filterConfig: FilterConfig) {
|
|
111
|
-
console.log('Filter applied:', filterConfig);
|
|
112
|
-
// Apply filter to your data
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
onFilterCleared() {
|
|
116
|
-
console.log('Filter cleared');
|
|
117
|
-
// Clear filter from your data
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
#### Complete Example with New Features (Backend Mode, allowMultipleRules):
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
import { Component, ViewChildren, QueryList } from '@angular/core';
|
|
126
|
-
import { CommonModule } from '@angular/common';
|
|
127
|
-
import { ColumnFilterComponent } from 'ngx-column-filter-popup';
|
|
128
|
-
import { FilterConfig, applyColumnFilter } from 'ngx-column-filter-popup';
|
|
129
|
-
|
|
130
|
-
interface User {
|
|
131
|
-
id: number;
|
|
132
|
-
firstName: string;
|
|
133
|
-
lastName: string;
|
|
134
|
-
email: string;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
@Component({
|
|
138
|
-
selector: 'app-example',
|
|
139
|
-
imports: [CommonModule, ColumnFilterComponent],
|
|
140
|
-
template: `
|
|
141
|
-
<table>
|
|
142
|
-
<thead>
|
|
143
|
-
<tr>
|
|
144
|
-
<th>
|
|
145
|
-
First Name
|
|
146
|
-
<lib-column-filter
|
|
147
|
-
columnName="first name"
|
|
148
|
-
columnKey="firstName"
|
|
149
|
-
[allowMultipleRules]="false"
|
|
150
|
-
[backendMode]="isBackendMode('firstName')"
|
|
151
|
-
(filterApplied)="onFilterApplied('firstName', $event)"
|
|
152
|
-
(filterCleared)="onFilterCleared('firstName')">
|
|
153
|
-
</lib-column-filter>
|
|
154
|
-
</th>
|
|
155
|
-
<th>
|
|
156
|
-
Last Name
|
|
157
|
-
<lib-column-filter
|
|
158
|
-
columnName="last name"
|
|
159
|
-
columnKey="lastName"
|
|
160
|
-
[allowMultipleRules]="true"
|
|
161
|
-
[backendMode]="isBackendMode('lastName')"
|
|
162
|
-
(filterApplied)="onFilterApplied('lastName', $event)"
|
|
163
|
-
(filterCleared)="onFilterCleared('lastName')">
|
|
164
|
-
</lib-column-filter>
|
|
165
|
-
</th>
|
|
166
|
-
<th>
|
|
167
|
-
Email
|
|
168
|
-
<lib-column-filter
|
|
169
|
-
columnName="email"
|
|
170
|
-
columnKey="email"
|
|
171
|
-
[backendMode]="isBackendMode('email')"
|
|
172
|
-
(filterApplied)="onFilterApplied('email', $event)"
|
|
173
|
-
(filterCleared)="onFilterCleared('email')">
|
|
174
|
-
</lib-column-filter>
|
|
175
|
-
</th>
|
|
176
|
-
</tr>
|
|
177
|
-
</thead>
|
|
178
|
-
<tbody>
|
|
179
|
-
<tr *ngFor="let user of filteredUsers">
|
|
180
|
-
<td>{{ user.firstName }}</td>
|
|
181
|
-
<td>{{ user.lastName }}</td>
|
|
182
|
-
<td>{{ user.email }}</td>
|
|
183
|
-
</tr>
|
|
184
|
-
</tbody>
|
|
185
|
-
</table>
|
|
186
|
-
<button (click)="clearAllFilters()">Clear All Filters</button>
|
|
187
|
-
`
|
|
188
|
-
})
|
|
189
|
-
export class ExampleComponent {
|
|
190
|
-
originalData: User[] = [
|
|
191
|
-
{ id: 1, firstName: 'John', lastName: 'Doe', email: 'john@example.com' }
|
|
192
|
-
];
|
|
193
|
-
filteredData: User[] = [...this.originalData];
|
|
194
|
-
|
|
195
|
-
// ✅ Unified filter storage - single source of truth
|
|
196
|
-
filters = new Map<string, FilterConfig | null>();
|
|
197
|
-
|
|
198
|
-
// ✅ Configuration: Which columns use backend mode
|
|
199
|
-
readonly backendModeColumns = new Set<string>(['firstName', 'email']);
|
|
200
|
-
|
|
201
|
-
@ViewChildren(ColumnFilterComponent) filterComponents!: QueryList<ColumnFilterComponent>;
|
|
202
|
-
|
|
203
|
-
// ✅ Generic filter handler - works for all columns
|
|
204
|
-
onFilterApplied(columnKey: string, filterConfig: FilterConfig) {
|
|
205
|
-
this.filters.set(columnKey, filterConfig);
|
|
206
|
-
|
|
207
|
-
if (this.isBackendMode(columnKey)) {
|
|
208
|
-
this.sendAllBackendFiltersToBackend();
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
this.applyAllFilters();
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// ✅ Generic filter clear handler
|
|
215
|
-
onFilterCleared(columnKey: string) {
|
|
216
|
-
this.filters.set(columnKey, null);
|
|
217
|
-
|
|
218
|
-
if (this.isBackendMode(columnKey)) {
|
|
219
|
-
this.sendAllBackendFiltersToBackend();
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
this.applyAllFilters();
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// ✅ Check if column uses backend mode
|
|
226
|
-
isBackendMode(columnKey: string): boolean {
|
|
227
|
-
return this.backendModeColumns.has(columnKey);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// ✅ Apply all filters - automatically skips backend mode columns
|
|
231
|
-
private applyAllFilters() {
|
|
232
|
-
let result = [...this.originalData];
|
|
233
|
-
|
|
234
|
-
this.filters.forEach((filterConfig, columnKey) => {
|
|
235
|
-
// Skip backend mode columns (handled by backend)
|
|
236
|
-
if (filterConfig && !this.isBackendMode(columnKey)) {
|
|
237
|
-
result = applyColumnFilter(result, columnKey, filterConfig);
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
this.filteredData = result;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// ✅ Clear all filters programmatically
|
|
245
|
-
clearAllFilters() {
|
|
246
|
-
this.filters.clear();
|
|
247
|
-
this.sendAllBackendFiltersToBackend();
|
|
248
|
-
this.filteredData = [...this.originalData];
|
|
249
|
-
|
|
250
|
-
// Clear UI state in all filter components (icons/inputs)
|
|
251
|
-
if (this.filterComponents) {
|
|
252
|
-
this.filterComponents.forEach((filter: ColumnFilterComponent) => {
|
|
253
|
-
filter.clearFilter();
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// ✅ Send all backend filters to API
|
|
259
|
-
private sendAllBackendFiltersToBackend() {
|
|
260
|
-
const activeFilters: Array<{
|
|
261
|
-
field: string;
|
|
262
|
-
matchType: string;
|
|
263
|
-
value: string;
|
|
264
|
-
fieldType: string;
|
|
265
|
-
}> = [];
|
|
266
|
-
|
|
267
|
-
this.backendModeColumns.forEach(columnKey => {
|
|
268
|
-
const filterConfig = this.filters.get(columnKey);
|
|
269
|
-
if (filterConfig && filterConfig.rules.length > 0) {
|
|
270
|
-
filterConfig.rules.forEach(rule => {
|
|
271
|
-
if (rule.value && rule.value.trim() !== '') {
|
|
272
|
-
activeFilters.push({
|
|
273
|
-
field: columnKey,
|
|
274
|
-
matchType: rule.matchType,
|
|
275
|
-
value: rule.value.trim(),
|
|
276
|
-
fieldType: filterConfig.fieldType || 'text'
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
const payload = { activeFilters, count: activeFilters.length };
|
|
284
|
-
// Send to your backend API
|
|
285
|
-
console.log('Backend payload:', payload);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
### Module-Based (Optional)
|
|
291
|
-
|
|
292
|
-
```typescript
|
|
293
|
-
import { NgModule } from '@angular/core';
|
|
294
|
-
import { ColumnFilterModule } from 'ngx-column-filter-popup';
|
|
295
|
-
|
|
296
|
-
@NgModule({
|
|
297
|
-
imports: [ColumnFilterModule],
|
|
298
|
-
// ...
|
|
299
|
-
})
|
|
300
|
-
export class YourModule {}
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
## Field Types
|
|
304
|
-
|
|
305
|
-
### Text Field (Default)
|
|
306
|
-
|
|
307
|
-
```html
|
|
308
|
-
<lib-column-filter
|
|
309
|
-
columnName="name"
|
|
310
|
-
columnKey="name"
|
|
311
|
-
fieldType="text"
|
|
312
|
-
(filterApplied)="onFilterApplied('name', $event)"
|
|
313
|
-
(filterCleared)="onFilterCleared('name')">
|
|
314
|
-
</lib-column-filter>
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### Currency Field
|
|
318
|
-
|
|
319
|
-
```html
|
|
320
|
-
<lib-column-filter
|
|
321
|
-
columnName="balance"
|
|
322
|
-
columnKey="balance"
|
|
323
|
-
fieldType="currency"
|
|
324
|
-
currencySymbol="$"
|
|
325
|
-
(filterApplied)="onFilterApplied('balance', $event)"
|
|
326
|
-
(filterCleared)="onFilterCleared('balance')">
|
|
327
|
-
</lib-column-filter>
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
### Age/Number Field
|
|
331
|
-
|
|
332
|
-
```html
|
|
333
|
-
<lib-column-filter
|
|
334
|
-
columnName="age"
|
|
335
|
-
columnKey="age"
|
|
336
|
-
fieldType="age"
|
|
337
|
-
(filterApplied)="onFilterApplied('age', $event)"
|
|
338
|
-
(filterCleared)="onFilterCleared('age')">
|
|
339
|
-
</lib-column-filter>
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
### Date Field
|
|
343
|
-
|
|
344
|
-
```html
|
|
345
|
-
<lib-column-filter
|
|
346
|
-
columnName="date"
|
|
347
|
-
columnKey="date"
|
|
348
|
-
fieldType="date"
|
|
349
|
-
(filterApplied)="onFilterApplied('date', $event)"
|
|
350
|
-
(filterCleared)="onFilterCleared('date')">
|
|
351
|
-
</lib-column-filter>
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
### Status Field
|
|
355
|
-
|
|
356
|
-
```html
|
|
357
|
-
<lib-column-filter
|
|
358
|
-
columnName="status"
|
|
359
|
-
columnKey="status"
|
|
360
|
-
fieldType="status"
|
|
361
|
-
[statusOptions]="['qualified', 'unqualified', 'negotiation', 'new']"
|
|
362
|
-
(filterApplied)="onFilterApplied('status', $event)"
|
|
363
|
-
(filterCleared)="onFilterCleared('status')">
|
|
364
|
-
</lib-column-filter>
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
> 💡 **Note**: All examples use generic handlers `onFilterApplied(columnKey, $event)` and `onFilterCleared(columnKey)` - no need for separate functions per filter!
|
|
368
|
-
|
|
369
|
-
## API Reference
|
|
370
|
-
|
|
371
|
-
### ColumnFilterComponent
|
|
372
|
-
|
|
373
|
-
#### Inputs
|
|
374
|
-
|
|
375
|
-
| Input | Type | Default | Description |
|
|
376
|
-
|-------|------|---------|-------------|
|
|
377
|
-
| `columnName` | `string` | `''` | Display name of the column (used in placeholder) |
|
|
378
|
-
| `columnKey` | `string` | `''` | Property name to filter on |
|
|
379
|
-
| `fieldType` | `FieldType` | `'text'` | Field type: 'text', 'currency', 'age', 'date', or 'status' |
|
|
380
|
-
| `currencySymbol` | `string` | `'$'` | Currency symbol for currency field type (optional) |
|
|
381
|
-
| `statusOptions` | `string[]` | `[]` | Array of status options for status field type (required for status) |
|
|
382
|
-
| `initialFilter` | `FilterConfig?` | `undefined` | Initial filter configuration (optional) |
|
|
383
|
-
| `placeholder` | `string?` | `undefined` | Custom placeholder text. Default: "Search by {columnName}" |
|
|
384
|
-
| `availableMatchTypes` | `MatchType[]?` | `undefined` | Customize available match types (optional) |
|
|
385
|
-
| `backendMode` | `boolean` | `false` | When true, component emits filter data for backend API instead of frontend filtering |
|
|
386
|
-
| `allowMultipleRules` | `boolean` | `true` | When false, hides Add/Remove Rule buttons (single rule only) |
|
|
387
|
-
|
|
388
|
-
#### Outputs
|
|
389
|
-
|
|
390
|
-
| Output | Type | Description |
|
|
391
|
-
|--------|------|-------------|
|
|
392
|
-
| `filterApplied` | `EventEmitter<FilterConfig>` | Emitted when filter is applied |
|
|
393
|
-
| `filterCleared` | `EventEmitter<void>` | Emitted when filter is cleared |
|
|
394
|
-
|
|
395
|
-
#### Public Methods
|
|
396
|
-
|
|
397
|
-
| Method | Description |
|
|
398
|
-
|--------|-------------|
|
|
399
|
-
| `clearFilter()` | Programmatically clear all filter rules and reset the filter |
|
|
400
|
-
|
|
401
|
-
### FilterConfig Interface
|
|
402
|
-
|
|
403
|
-
```typescript
|
|
404
|
-
type FieldType = 'text' | 'currency' | 'age' | 'date' | 'status';
|
|
405
|
-
|
|
406
|
-
interface FilterConfig {
|
|
407
|
-
rules: FilterRule[];
|
|
408
|
-
globalMatchMode?: GlobalMatchMode; // 'match-all-rules' | 'match-any-rule'
|
|
409
|
-
fieldType?: FieldType;
|
|
410
|
-
statusOptions?: string[];
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
interface FilterRule {
|
|
414
|
-
id: string;
|
|
415
|
-
matchType: MatchType;
|
|
416
|
-
value: string;
|
|
417
|
-
}
|
|
10
|
+
ng generate component component-name
|
|
418
11
|
```
|
|
419
12
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
#### applyColumnFilter
|
|
423
|
-
|
|
424
|
-
Apply filter rules to a dataset:
|
|
425
|
-
|
|
426
|
-
```typescript
|
|
427
|
-
import { applyColumnFilter } from 'ngx-column-filter-popup';
|
|
428
|
-
|
|
429
|
-
const filteredData = applyColumnFilter(
|
|
430
|
-
data,
|
|
431
|
-
'columnKey',
|
|
432
|
-
filterConfig
|
|
433
|
-
);
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
#### itemMatchesFilter
|
|
437
|
-
|
|
438
|
-
Check if a single item matches filter rules:
|
|
439
|
-
|
|
440
|
-
```typescript
|
|
441
|
-
import { itemMatchesFilter } from 'ngx-column-filter-popup';
|
|
13
|
+
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
|
|
442
14
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
'columnKey',
|
|
446
|
-
filterConfig
|
|
447
|
-
);
|
|
15
|
+
```bash
|
|
16
|
+
ng generate --help
|
|
448
17
|
```
|
|
449
18
|
|
|
450
|
-
##
|
|
451
|
-
|
|
452
|
-
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.
|
|
453
|
-
|
|
454
|
-
### Backend Mode Example:
|
|
455
|
-
|
|
456
|
-
```typescript
|
|
457
|
-
import { Component } from '@angular/core';
|
|
458
|
-
import { ColumnFilterComponent } from 'ngx-column-filter-popup';
|
|
459
|
-
import { FilterConfig } from 'ngx-column-filter-popup';
|
|
460
|
-
|
|
461
|
-
@Component({
|
|
462
|
-
selector: 'app-example',
|
|
463
|
-
imports: [ColumnFilterComponent],
|
|
464
|
-
template: `
|
|
465
|
-
<lib-column-filter
|
|
466
|
-
columnName="first name"
|
|
467
|
-
columnKey="firstName"
|
|
468
|
-
[backendMode]="true"
|
|
469
|
-
(filterApplied)="onFilterApplied($event)"
|
|
470
|
-
(filterCleared)="onFilterCleared()">
|
|
471
|
-
</lib-column-filter>
|
|
472
|
-
`
|
|
473
|
-
})
|
|
474
|
-
export class ExampleComponent {
|
|
475
|
-
filters = new Map<string, FilterConfig | null>();
|
|
476
|
-
readonly backendModeColumns = new Set<string>(['firstName', 'email']);
|
|
477
|
-
|
|
478
|
-
onFilterApplied(columnKey: string, filterConfig: FilterConfig) {
|
|
479
|
-
this.filters.set(columnKey, filterConfig);
|
|
480
|
-
this.sendToBackend();
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
onFilterCleared(columnKey: string) {
|
|
484
|
-
this.filters.set(columnKey, null);
|
|
485
|
-
this.sendToBackend();
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
private sendToBackend() {
|
|
489
|
-
const activeFilters: Array<{
|
|
490
|
-
field: string;
|
|
491
|
-
matchType: string;
|
|
492
|
-
value: string;
|
|
493
|
-
fieldType: string;
|
|
494
|
-
}> = [];
|
|
495
|
-
|
|
496
|
-
this.backendModeColumns.forEach(columnKey => {
|
|
497
|
-
const filterConfig = this.filters.get(columnKey);
|
|
498
|
-
if (filterConfig && filterConfig.rules.length > 0) {
|
|
499
|
-
filterConfig.rules.forEach(rule => {
|
|
500
|
-
if (rule.value && rule.value.trim() !== '') {
|
|
501
|
-
activeFilters.push({
|
|
502
|
-
field: columnKey,
|
|
503
|
-
matchType: rule.matchType,
|
|
504
|
-
value: rule.value.trim(),
|
|
505
|
-
fieldType: filterConfig.fieldType || 'text'
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
});
|
|
19
|
+
## Building
|
|
511
20
|
|
|
512
|
-
|
|
513
|
-
activeFilters: activeFilters,
|
|
514
|
-
count: activeFilters.length
|
|
515
|
-
};
|
|
21
|
+
To build the library, run:
|
|
516
22
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
console.log('Backend payload:', payload);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
```
|
|
523
|
-
|
|
524
|
-
### Backend Payload Format:
|
|
525
|
-
|
|
526
|
-
```json
|
|
527
|
-
{
|
|
528
|
-
"activeFilters": [
|
|
529
|
-
{
|
|
530
|
-
"field": "firstName",
|
|
531
|
-
"matchType": "contains",
|
|
532
|
-
"value": "John",
|
|
533
|
-
"fieldType": "text"
|
|
534
|
-
},
|
|
535
|
-
{
|
|
536
|
-
"field": "email",
|
|
537
|
-
"matchType": "contains",
|
|
538
|
-
"value": "example",
|
|
539
|
-
"fieldType": "text"
|
|
540
|
-
}
|
|
541
|
-
],
|
|
542
|
-
"count": 2
|
|
543
|
-
}
|
|
23
|
+
```bash
|
|
24
|
+
ng build column-filter-popup
|
|
544
25
|
```
|
|
545
26
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
Control whether users can add multiple filter rules:
|
|
549
|
-
|
|
550
|
-
```html
|
|
551
|
-
<!-- Multiple rules allowed (default) -->
|
|
552
|
-
<lib-column-filter
|
|
553
|
-
columnName="name"
|
|
554
|
-
columnKey="name"
|
|
555
|
-
[allowMultipleRules]="true">
|
|
556
|
-
</lib-column-filter>
|
|
557
|
-
|
|
558
|
-
<!-- Single rule only (Add/Remove buttons hidden) -->
|
|
559
|
-
<lib-column-filter
|
|
560
|
-
columnName="email"
|
|
561
|
-
columnKey="email"
|
|
562
|
-
[allowMultipleRules]="false">
|
|
563
|
-
</lib-column-filter>
|
|
564
|
-
```
|
|
27
|
+
This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
|
|
565
28
|
|
|
566
|
-
|
|
567
|
-
- ✅ Add Rule button is hidden
|
|
568
|
-
- ✅ Remove Rule buttons are hidden
|
|
569
|
-
- ✅ Global Match Mode toggle is hidden
|
|
570
|
-
- ✅ Users can only use a single filter rule
|
|
29
|
+
### Publishing the Library
|
|
571
30
|
|
|
572
|
-
|
|
573
|
-
- ✅ All features work normally
|
|
574
|
-
- ✅ Users can add multiple rules
|
|
575
|
-
- ✅ Match All/Match Any toggle is available
|
|
31
|
+
Once the project is built, you can publish your library by following these steps:
|
|
576
32
|
|
|
577
|
-
|
|
33
|
+
1. Navigate to the `dist` directory:
|
|
34
|
+
```bash
|
|
35
|
+
cd dist/column-filter-popup
|
|
36
|
+
```
|
|
578
37
|
|
|
579
|
-
|
|
38
|
+
2. Run the `npm publish` command to publish your library to the npm registry:
|
|
39
|
+
```bash
|
|
40
|
+
npm publish
|
|
41
|
+
```
|
|
580
42
|
|
|
581
|
-
|
|
582
|
-
import { Component, ViewChild } from '@angular/core';
|
|
583
|
-
import { ColumnFilterComponent } from 'ngx-column-filter-popup';
|
|
43
|
+
## Running unit tests
|
|
584
44
|
|
|
585
|
-
|
|
586
|
-
template: `
|
|
587
|
-
<lib-column-filter
|
|
588
|
-
#nameFilter
|
|
589
|
-
columnName="name"
|
|
590
|
-
columnKey="name">
|
|
591
|
-
</lib-column-filter>
|
|
592
|
-
<button (click)="clearFilter()">Clear Filter</button>
|
|
593
|
-
`
|
|
594
|
-
})
|
|
595
|
-
export class ExampleComponent {
|
|
596
|
-
@ViewChild('nameFilter') filter!: ColumnFilterComponent;
|
|
45
|
+
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
|
|
597
46
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
601
|
-
}
|
|
47
|
+
```bash
|
|
48
|
+
ng test
|
|
602
49
|
```
|
|
603
50
|
|
|
604
|
-
##
|
|
605
|
-
|
|
606
|
-
**✅ Modern Implementation using Generic Handlers (Recommended):**
|
|
607
|
-
|
|
608
|
-
```typescript
|
|
609
|
-
import { Component, ViewChildren, QueryList } from '@angular/core';
|
|
610
|
-
import { CommonModule } from '@angular/common';
|
|
611
|
-
import { ColumnFilterComponent } from 'ngx-column-filter-popup';
|
|
612
|
-
import { FilterConfig, applyColumnFilter } from 'ngx-column-filter-popup';
|
|
613
|
-
|
|
614
|
-
interface User {
|
|
615
|
-
id: number;
|
|
616
|
-
firstName: string;
|
|
617
|
-
lastName: string;
|
|
618
|
-
email: string;
|
|
619
|
-
age: number;
|
|
620
|
-
balance: number;
|
|
621
|
-
joinDate: string;
|
|
622
|
-
status: string;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
@Component({
|
|
626
|
-
selector: 'app-user-list',
|
|
627
|
-
imports: [CommonModule, ColumnFilterComponent],
|
|
628
|
-
template: `
|
|
629
|
-
<button (click)="clearAllFilters()">Clear All Filters</button>
|
|
630
|
-
<table>
|
|
631
|
-
<thead>
|
|
632
|
-
<tr>
|
|
633
|
-
<th>
|
|
634
|
-
First Name
|
|
635
|
-
<lib-column-filter
|
|
636
|
-
columnName="first name"
|
|
637
|
-
columnKey="firstName"
|
|
638
|
-
[allowMultipleRules]="false"
|
|
639
|
-
[backendMode]="isBackendMode('firstName')"
|
|
640
|
-
(filterApplied)="onFilterApplied('firstName', $event)"
|
|
641
|
-
(filterCleared)="onFilterCleared('firstName')">
|
|
642
|
-
</lib-column-filter>
|
|
643
|
-
</th>
|
|
644
|
-
<th>
|
|
645
|
-
Last Name
|
|
646
|
-
<lib-column-filter
|
|
647
|
-
columnName="last name"
|
|
648
|
-
columnKey="lastName"
|
|
649
|
-
(filterApplied)="onFilterApplied('lastName', $event)"
|
|
650
|
-
(filterCleared)="onFilterCleared('lastName')">
|
|
651
|
-
</lib-column-filter>
|
|
652
|
-
</th>
|
|
653
|
-
<th>
|
|
654
|
-
Email
|
|
655
|
-
<lib-column-filter
|
|
656
|
-
columnName="email"
|
|
657
|
-
columnKey="email"
|
|
658
|
-
[backendMode]="isBackendMode('email')"
|
|
659
|
-
(filterApplied)="onFilterApplied('email', $event)"
|
|
660
|
-
(filterCleared)="onFilterCleared('email')">
|
|
661
|
-
</lib-column-filter>
|
|
662
|
-
</th>
|
|
663
|
-
<th>
|
|
664
|
-
Age
|
|
665
|
-
<lib-column-filter
|
|
666
|
-
columnName="age"
|
|
667
|
-
columnKey="age"
|
|
668
|
-
fieldType="age"
|
|
669
|
-
(filterApplied)="onFilterApplied('age', $event)"
|
|
670
|
-
(filterCleared)="onFilterCleared('age')">
|
|
671
|
-
</lib-column-filter>
|
|
672
|
-
</th>
|
|
673
|
-
<th>
|
|
674
|
-
Balance
|
|
675
|
-
<lib-column-filter
|
|
676
|
-
columnName="balance"
|
|
677
|
-
columnKey="balance"
|
|
678
|
-
fieldType="currency"
|
|
679
|
-
currencySymbol="$"
|
|
680
|
-
(filterApplied)="onFilterApplied('balance', $event)"
|
|
681
|
-
(filterCleared)="onFilterCleared('balance')">
|
|
682
|
-
</lib-column-filter>
|
|
683
|
-
</th>
|
|
684
|
-
<th>
|
|
685
|
-
Join Date
|
|
686
|
-
<lib-column-filter
|
|
687
|
-
columnName="join date"
|
|
688
|
-
columnKey="joinDate"
|
|
689
|
-
fieldType="date"
|
|
690
|
-
(filterApplied)="onFilterApplied('joinDate', $event)"
|
|
691
|
-
(filterCleared)="onFilterCleared('joinDate')">
|
|
692
|
-
</lib-column-filter>
|
|
693
|
-
</th>
|
|
694
|
-
<th>
|
|
695
|
-
Status
|
|
696
|
-
<lib-column-filter
|
|
697
|
-
columnName="status"
|
|
698
|
-
columnKey="status"
|
|
699
|
-
fieldType="status"
|
|
700
|
-
[statusOptions]="statusOptions"
|
|
701
|
-
(filterApplied)="onFilterApplied('status', $event)"
|
|
702
|
-
(filterCleared)="onFilterCleared('status')">
|
|
703
|
-
</lib-column-filter>
|
|
704
|
-
</th>
|
|
705
|
-
</tr>
|
|
706
|
-
</thead>
|
|
707
|
-
<tbody>
|
|
708
|
-
<tr *ngFor="let user of filteredUsers">
|
|
709
|
-
<td>{{ user.firstName }}</td>
|
|
710
|
-
<td>{{ user.lastName }}</td>
|
|
711
|
-
<td>{{ user.email }}</td>
|
|
712
|
-
<td>{{ user.age }}</td>
|
|
713
|
-
<td>${{ user.balance }}</td>
|
|
714
|
-
<td>{{ user.joinDate }}</td>
|
|
715
|
-
<td>{{ user.status }}</td>
|
|
716
|
-
</tr>
|
|
717
|
-
</tbody>
|
|
718
|
-
</table>
|
|
719
|
-
`
|
|
720
|
-
})
|
|
721
|
-
export class UserListComponent {
|
|
722
|
-
users: User[] = [
|
|
723
|
-
{ id: 1, firstName: 'John', lastName: 'Doe', email: 'john@example.com', age: 30, balance: 50000, joinDate: '2020-01-15', status: 'active' }
|
|
724
|
-
];
|
|
725
|
-
|
|
726
|
-
filteredUsers: User[] = [...this.users];
|
|
727
|
-
statusOptions = ['active', 'inactive', 'on-leave'];
|
|
728
|
-
|
|
729
|
-
// ✅ Unified filter storage - single source of truth
|
|
730
|
-
filters = new Map<string, FilterConfig | null>();
|
|
731
|
-
|
|
732
|
-
// ✅ Configuration: Which columns use backend mode
|
|
733
|
-
readonly backendModeColumns = new Set<string>(['firstName', 'email']);
|
|
734
|
-
|
|
735
|
-
@ViewChildren(ColumnFilterComponent) filterComponents!: QueryList<ColumnFilterComponent>;
|
|
736
|
-
|
|
737
|
-
// ✅ Generic filter handler - works for ALL columns (no separate functions needed!)
|
|
738
|
-
onFilterApplied(columnKey: string, filterConfig: FilterConfig): void {
|
|
739
|
-
this.filters.set(columnKey, filterConfig);
|
|
740
|
-
|
|
741
|
-
if (this.isBackendMode(columnKey)) {
|
|
742
|
-
this.sendAllBackendFiltersToBackend();
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
this.applyAllFilters();
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// ✅ Generic filter clear handler - works for ALL columns
|
|
749
|
-
onFilterCleared(columnKey: string): void {
|
|
750
|
-
this.filters.set(columnKey, null);
|
|
751
|
-
|
|
752
|
-
if (this.isBackendMode(columnKey)) {
|
|
753
|
-
this.sendAllBackendFiltersToBackend();
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
this.applyAllFilters();
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
// ✅ Check if column uses backend mode
|
|
760
|
-
isBackendMode(columnKey: string): boolean {
|
|
761
|
-
return this.backendModeColumns.has(columnKey);
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
// ✅ Apply all filters - automatically skips backend mode columns
|
|
765
|
-
private applyAllFilters(): void {
|
|
766
|
-
let result = [...this.users];
|
|
767
|
-
|
|
768
|
-
this.filters.forEach((filterConfig, columnKey) => {
|
|
769
|
-
// Skip backend mode columns (handled by backend)
|
|
770
|
-
if (filterConfig && !this.isBackendMode(columnKey)) {
|
|
771
|
-
result = applyColumnFilter(result, columnKey, filterConfig);
|
|
772
|
-
}
|
|
773
|
-
});
|
|
774
|
-
|
|
775
|
-
this.filteredUsers = result;
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// ✅ Clear all filters programmatically
|
|
779
|
-
clearAllFilters(): void {
|
|
780
|
-
this.filters.clear();
|
|
781
|
-
this.sendAllBackendFiltersToBackend();
|
|
782
|
-
this.filteredUsers = [...this.users];
|
|
783
|
-
|
|
784
|
-
// Clear UI state in all filter components (icons/inputs)
|
|
785
|
-
if (this.filterComponents) {
|
|
786
|
-
this.filterComponents.forEach((filter: ColumnFilterComponent) => {
|
|
787
|
-
filter.clearFilter();
|
|
788
|
-
});
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
// ✅ Send all backend filters to API
|
|
793
|
-
private sendAllBackendFiltersToBackend(): void {
|
|
794
|
-
const activeFilters: Array<{
|
|
795
|
-
field: string;
|
|
796
|
-
matchType: string;
|
|
797
|
-
value: string;
|
|
798
|
-
fieldType: string;
|
|
799
|
-
}> = [];
|
|
51
|
+
## Running end-to-end tests
|
|
800
52
|
|
|
801
|
-
|
|
802
|
-
const filterConfig = this.filters.get(columnKey);
|
|
803
|
-
if (filterConfig && filterConfig.rules.length > 0) {
|
|
804
|
-
filterConfig.rules.forEach(rule => {
|
|
805
|
-
if (rule.value && rule.value.trim() !== '') {
|
|
806
|
-
activeFilters.push({
|
|
807
|
-
field: columnKey,
|
|
808
|
-
matchType: rule.matchType,
|
|
809
|
-
value: rule.value.trim(),
|
|
810
|
-
fieldType: filterConfig.fieldType || 'text'
|
|
811
|
-
});
|
|
812
|
-
}
|
|
813
|
-
});
|
|
814
|
-
}
|
|
815
|
-
});
|
|
53
|
+
For end-to-end (e2e) testing, run:
|
|
816
54
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
console.log('Backend payload:', payload);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
55
|
+
```bash
|
|
56
|
+
ng e2e
|
|
822
57
|
```
|
|
823
58
|
|
|
824
|
-
|
|
825
|
-
- ✅ **No separate functions per filter** - One `onFilterApplied()` handles all columns
|
|
826
|
-
- ✅ **Easy to add new filters** - Just add HTML, no new functions needed
|
|
827
|
-
- ✅ **Clean and maintainable** - Map-based storage, generic handlers
|
|
828
|
-
- ✅ **Backend mode support** - Configurable per column
|
|
829
|
-
- ✅ **Single source of truth** - All filters in one Map
|
|
830
|
-
|
|
831
|
-
## 📖 Documentation
|
|
832
|
-
|
|
833
|
-
### For Beginners:
|
|
834
|
-
- **[Getting Started Tutorial](./GETTING_STARTED.md)** - Step-by-step guide with examples
|
|
835
|
-
- What to import in TypeScript
|
|
836
|
-
- How to setup component
|
|
837
|
-
- Complete working examples
|
|
838
|
-
- Common patterns
|
|
839
|
-
|
|
840
|
-
### For Advanced Users:
|
|
841
|
-
- **[Complete Documentation](./DOCUMENTATION.md)** - Full API reference
|
|
842
|
-
- All inputs and outputs
|
|
843
|
-
- Utility functions
|
|
844
|
-
- Type definitions
|
|
845
|
-
- Match modes explained
|
|
846
|
-
- Data structure adaptation
|
|
847
|
-
|
|
848
|
-
- **[Usage Examples](./USAGE_EXAMPLES.md)** - Advanced patterns
|
|
849
|
-
- Programmatic filter control
|
|
850
|
-
- Multiple filters management
|
|
851
|
-
- Custom configurations
|
|
852
|
-
|
|
853
|
-
- **[Deployment Guide](./DEPLOYMENT.md)** - Deploy your Angular app
|
|
854
|
-
- GitHub Pages
|
|
855
|
-
- Vercel
|
|
856
|
-
- Netlify
|
|
857
|
-
- Firebase Hosting
|
|
858
|
-
|
|
859
|
-
## Styling
|
|
860
|
-
|
|
861
|
-
The component uses SCSS and includes default styles. You can customize the appearance by overriding CSS classes:
|
|
862
|
-
|
|
863
|
-
- `.column-filter-wrapper` - Main wrapper
|
|
864
|
-
- `.filter-trigger` - Filter button
|
|
865
|
-
- `.filter-dropdown` - Dropdown container
|
|
866
|
-
- `.filter-rule` - Individual filter rule
|
|
867
|
-
- `.btn-apply` - Apply button
|
|
868
|
-
- `.btn-clear` - Clear button
|
|
869
|
-
|
|
870
|
-
## Browser Support
|
|
871
|
-
|
|
872
|
-
- Chrome (latest)
|
|
873
|
-
- Firefox (latest)
|
|
874
|
-
- Safari (latest)
|
|
875
|
-
- Edge (latest)
|
|
876
|
-
|
|
877
|
-
## Requirements
|
|
878
|
-
|
|
879
|
-
- Angular 14+
|
|
880
|
-
- TypeScript 4.7+
|
|
881
|
-
|
|
882
|
-
## License
|
|
883
|
-
|
|
884
|
-
MIT
|
|
885
|
-
|
|
886
|
-
## Author
|
|
887
|
-
|
|
888
|
-
Made with ❤️ by Shivam Sharma
|
|
59
|
+
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
|
|
889
60
|
|
|
890
|
-
##
|
|
61
|
+
## Additional Resources
|
|
891
62
|
|
|
892
|
-
|
|
63
|
+
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|