ng-firebase-table-kxp 1.0.8 → 1.0.9

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 CHANGED
@@ -1,603 +1,603 @@
1
- # NG Firebase Table KXP
2
-
3
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
-
5
- NG Firebase Table KXP is a production-ready Angular data table created by **KXP Tech** to power enterprise dashboards backed by Firebase Firestore. It wraps complex Firestore querying, pagination, filtering, and material design styling into a plug-and-play module so teams can ship data-rich experiences faster.
6
-
7
- ## Table of Contents
8
- - [NG Firebase Table KXP](#ng-firebase-table-kxp)
9
- - [Table of Contents](#table-of-contents)
10
- - [Why Firebase Table KXP](#why-firebase-table-kxp)
11
- - [Core Features](#core-features)
12
- - [Installation \& Setup](#installation--setup)
13
- - [Configuration Overview](#configuration-overview)
14
- - [API Reference](#api-reference)
15
- - [TableData](#tabledata)
16
- - [Column](#column)
17
- - [Tab \& TabData](#tab--tabdata)
18
- - [FilterableOption](#filterableoption)
19
- - [Condition](#condition)
20
- - [Arrange](#arrange)
21
- - [Image](#image)
22
- - [Data Export (Download)](#data-export-download)
23
- - [How It Works](#how-it-works)
24
- - [Basic Example](#basic-example)
25
- - [Advanced Example with Totals](#advanced-example-with-totals)
26
- - [Filtering Data Before Export](#filtering-data-before-export)
27
- - [Using Custom Pipes for Formatting](#using-custom-pipes-for-formatting)
28
- - [CSV vs XLSX Format](#csv-vs-xlsx-format)
29
- - [Important Notes](#important-notes)
30
- - [Powered by KXP Tech](#powered-by-kxp-tech)
31
- - [Contributing \& License](#contributing--license)
32
- - [Support the Project](#support-the-project)
33
-
34
- ## Why Firebase Table KXP
35
-
36
- - Built and battle-tested at **KXP Tech** across projects with thousands of active users
37
- - Eliminates weeks of custom table work; drop it in and configure
38
- - Handles Firestore quirks (pagination, missing indexes, fallbacks) automatically
39
- - Provides a strongly typed API that scales with project complexity
40
- - Plays nicely with Angular Material, Firebase, and Angular Fire
41
-
42
- ## Core Features
43
-
44
- - Full Firestore integration with server-side and client-side pagination
45
- - Text, date, and custom filters with automatic index fallback when needed
46
- - Configurable columns for relations, custom renderers, actions, and exports
47
- - Built-in CSV export hooks and actionable buttons per row
48
- - Pagination, sorting, and filter state shared between tabs
49
- - Optional color theming, tabbed datasets, and custom navigation per row
50
- - Works seamlessly with Angular 15, Angular Material, Angular Fire, and RxJS
51
-
52
- ## Installation & Setup
53
-
54
- ```bash
55
- npm install @adryanmmm/ng-firebase-table-kxp
56
- ```
57
-
58
- The host application should already depend on:
59
-
60
- - Angular 15 packages: `@angular/common`, `@angular/core`, `@angular/forms`, `@angular/router`, `@angular/cdk`, `@angular/material`
61
- - Firebase stack: `@angular/fire` (7.x) and `firebase` (9.x)
62
- - Utilities: `ngx-toastr` (16.x), `moment` (2.29.x), `ngx-mask` (15.x)
63
-
64
-
65
- 1. **Import the module**
66
- ```typescript
67
- import { NgFirebaseTableKxpModule } from '@adryanmmm/ng-firebase-table-kxp';
68
-
69
- @NgModule({
70
- imports: [
71
- BrowserModule,
72
- BrowserAnimationsModule,
73
- AngularFireModule.initializeApp(environment.firebase),
74
- AngularFirestoreModule,
75
- ToastrModule.forRoot(),
76
- NgxMaskModule.forRoot(),
77
- NgFirebaseTableKxpModule
78
- ]
79
- })
80
- export class AppModule {}
81
- ```
82
-
83
- 2. **Drop the component**
84
- ```typescript
85
- @Component({
86
- selector: 'app-users',
87
- template: `
88
- <lib-table
89
- [data]="table"
90
- [downloadTable]="downloadTable">
91
- </lib-table>
92
- `
93
- })
94
- export class UsersComponent {
95
- table: TableData = {
96
- name: 'users',
97
- collection: 'users',
98
- collectionRef: this.firestore.collection('users').ref,
99
- pagination: true,
100
- download: true,
101
- displayedColumns: [
102
- { property: 'name', title: 'Name', isFilterable: true, isSortable: true },
103
- { property: 'email', title: 'Email', isFilterable: true }
104
- ]
105
- };
106
-
107
- downloadTable = (arrange: any, conditions: any[]) => {
108
- // export logic
109
- };
110
-
111
- constructor(private firestore: AngularFirestore) {}
112
- }
113
- ```
114
-
115
- 3. **Make sure global styles include**
116
- ```scss
117
- @import '@angular/material/prebuilt-themes/indigo-pink.css';
118
- @import 'ngx-toastr/toastr';
119
- ```
120
-
121
- ## Configuration Overview
122
-
123
- - `TableData` defines the table behavior: source collection, pagination, sorting, tabs, exported totals, and custom navigation.
124
- - `Column` describes each displayed column: filters, relations, custom templates, icon actions, download buttons, and data formatting.
125
- - `Condition` lets you predefine Firestore `where` clauses applied before any user filters.
126
- - Optional hooks like `filterFn`, `actionButton`, and `tabs` enable richer UX without duplicating logic.
127
- - Automatic client-side fallback when Firestore reports missing composite indexes, plus logging of the index link to a `missingIndexes` collection.
128
- - Support for joining related collections, counting related documents per row, and managing dialog-driven actions via `iconClass` or `actionButton` handlers.
129
- - Compatible with SSR, lazy-loaded modules, and Angular standalone components.
130
-
131
- ## API Reference
132
-
133
- This section provides detailed documentation for all configuration interfaces available in the library.
134
-
135
- ### TableData
136
-
137
- The main configuration object that defines the entire table behavior.
138
-
139
- | Property | Type | Required | Description |
140
- |----------|------|----------|-------------|
141
- | `name` | `string` | ✅ | Unique identifier for the table. Used internally for caching and state management. |
142
- | `collection` | `string` | ✅ | Name of the Firestore collection to query. |
143
- | `collectionRef` | `CollectionReference` | ✅ | Reference to the Firestore collection. Use `firestore.collection('name').ref`. |
144
- | `displayedColumns` | `Column[]` | ✅ | Array of column definitions. Defines what data to display and how. See [Column](#column). |
145
- | `pagination` | `boolean` | ✅ | Enable or disable pagination. When `true`, data is loaded in batches. |
146
- | `download` | `boolean` | ✅ | Enable or disable the export/download functionality. |
147
- | `sortBy` | `{field: string, order: OrderByDirection}` | ❌ | Default sorting configuration. `order` can be `'asc'` or `'desc'`. |
148
- | `conditions` | `Condition[]` | ❌ | Array of Firestore where clauses applied before user filters. See [Condition](#condition). |
149
- | `filterFn` | `(item: any) => boolean` | ❌ | Custom client-side filter function. Applied after Firestore queries. |
150
- | `filterableOptions` | `FilterableOption[]` | ❌ | Predefined filter options displayed as buttons. See [FilterableOption](#filterableoption). |
151
- | `url` | `string` | ❌ | Base URL for row navigation. Row clicks navigate to `{url}/{row.id}`. |
152
- | `isNotClickable` | `boolean` | ❌ | When `true`, disables row click navigation. |
153
- | `color` | `{bg: string, text: string}` | ❌ | CSS classes for custom table theming. `bg` for background, `text` for text color. |
154
- | `totalRef` | `{ref: DocumentReference, field: string}[]` | ❌ | References to documents containing total counts. Displayed in the table footer. |
155
- | `actionButton` | `ActionButton` | ❌ | Configuration for a primary action button (e.g., "Add New"). |
156
- | `tabs` | `Tab` | ❌ | Configuration for tabbed data views. See [Tab & TabData](#tab--tabdata). |
157
-
158
- **ActionButton Properties:**
159
- - `label` (string): Button text
160
- - `routerLink` (string): Navigation route
161
- - `icon` (string): CSS class for icon (e.g., FontAwesome)
162
- - `colorClass` (string): CSS class for button styling
163
- - `method` ((row, event) => any): Custom click handler
164
- - `condition` ((row) => boolean): Function to conditionally show the button
165
-
166
- **Example:**
167
- ```typescript
168
- const tableData: TableData = {
169
- name: 'products',
170
- collection: 'products',
171
- collectionRef: this.firestore.collection('products').ref,
172
- pagination: true,
173
- download: true,
174
- sortBy: { field: 'createdAt', order: 'desc' },
175
- conditions: [
176
- { operator: '==', firestoreProperty: 'active', dashProperty: true }
177
- ],
178
- displayedColumns: [/* ... */],
179
- actionButton: {
180
- label: 'Add Product',
181
- routerLink: '/products/new',
182
- icon: 'fa fa-plus',
183
- colorClass: 'bg-blue-500'
184
- }
185
- };
186
- ```
187
-
188
- ---
189
-
190
- ### Column
191
-
192
- Defines how each column is displayed, filtered, and interacted with.
193
-
194
- | Property | Type | Required | Description |
195
- |----------|------|----------|-------------|
196
- | `property` | `string` | ✅ | Name of the property in the data object to display. |
197
- | `title` | `string` | ❌ | Column header text. Defaults to `property` if not provided. |
198
- | `charLimit` | `number` | ❌ | Maximum characters to display. Shows tooltip with full text on hover. |
199
- | `pipe` | `PipeTransform` | ❌ | Angular pipe for formatting values (e.g., `DatePipe`, `CurrencyPipe`). |
200
- | `calculateValue` | `(row: any) => any` | ❌ | Function to compute the display value from the row data. |
201
- | `isSortable` | `boolean` | ❌ | Enable sorting for this column. |
202
- | `isFilterable` | `boolean` | ❌ | Enable text-based filtering for this column. |
203
- | `isFilterableByDate` | `boolean` | ❌ | Enable date range filtering. Shows date picker inputs. |
204
- | `filterPredicates` | `string[]` | ❌ | Additional properties to include when filtering this column. |
205
- | `hasLink` | `boolean \| string` | ❌ | Make the cell value a clickable link. If string, uses as the navigation path. |
206
- | `hasDownload` | `boolean \| string` | ❌ | Add download icon to the cell. If string, uses as the download URL property. |
207
- | `arrayField` | `string` | ❌ | When data is an array of objects, specify which field to display. |
208
- | `method` | `(row: any, event?: any) => any` | ❌ | Custom click handler for the cell. |
209
- | `image` | `Image` | ❌ | Configuration for displaying images. See [Image](#image). |
210
- | `iconClass` | `IconClass[]` | ❌ | Array of icon/button configurations for action buttons in the cell. |
211
- | `relation` | `Relation` | ❌ | Configuration for joining related collection data. |
212
- | `queryLength` | `QueryLength` | ❌ | Configuration for counting related documents. |
213
-
214
- **IconClass Properties:**
215
- - `class` (string): CSS classes for the icon/button
216
- - `text` (string): Text to display alongside icon
217
- - `condition` ((row) => any): Function to conditionally show the icon
218
- - `buttonMethod` ((row, event) => any): Click handler for the icon
219
-
220
- **Relation Properties:**
221
- - `collection` (string): Name of the related collection
222
- - `property` (string): Property in current row containing the related document ID
223
- - `newProperty` (string): Property from related document to fetch and display
224
-
225
- **QueryLength Properties:**
226
- - `collection` (string): Collection to count documents from
227
- - `property` (string): Field to match in the query
228
- - `operator` (WhereFilterOp): Firestore operator (e.g., `'=='`, `'>'`)
229
- - `value` (string): Property from current row to use as query value
230
-
231
- **Example:**
232
- ```typescript
233
- const columns: Column[] = [
234
- {
235
- property: 'name',
236
- title: 'Product Name',
237
- isFilterable: true,
238
- isSortable: true,
239
- charLimit: 50
240
- },
241
- {
242
- property: 'price',
243
- title: 'Price',
244
- pipe: new CurrencyPipe('en-US'),
245
- isSortable: true
246
- },
247
- {
248
- property: 'createdAt',
249
- title: 'Created',
250
- pipe: new DatePipe('en-US'),
251
- isFilterableByDate: true
252
- },
253
- {
254
- property: 'categoryName',
255
- title: 'Category',
256
- relation: {
257
- collection: 'categories',
258
- property: 'categoryId',
259
- newProperty: 'name'
260
- }
261
- },
262
- {
263
- property: 'actions',
264
- title: 'Actions',
265
- iconClass: [
266
- {
267
- class: 'fa fa-edit text-blue-500 cursor-pointer',
268
- buttonMethod: (row) => this.editProduct(row)
269
- },
270
- {
271
- class: 'fa fa-trash text-red-500 cursor-pointer',
272
- buttonMethod: (row) => this.deleteProduct(row),
273
- condition: (row) => row.canDelete === true
274
- }
275
- ]
276
- }
277
- ];
278
- ```
279
-
280
- ---
281
-
282
- ### Tab & TabData
283
-
284
- Configure tabbed views for switching between different data sets within the same table.
285
-
286
- **Tab Interface:**
287
-
288
- | Property | Type | Required | Description |
289
- |----------|------|----------|-------------|
290
- | `method` | `(tab: any, event?: any) => any` | ✅ | Function called when a tab is clicked. Use to switch data or apply filters. |
291
- | `tabsData` | `TabData[]` | ✅ | Array of tab configurations. |
292
-
293
- **TabData Interface:**
294
-
295
- | Property | Type | Required | Description |
296
- |----------|------|----------|-------------|
297
- | `label` | `string` | ✅ | Text displayed on the tab. |
298
- | `counter` | `number` | ❌ | Optional counter badge displayed next to the label. |
299
- | `counterClass` | `string` | ❌ | CSS class for styling the counter badge. |
300
-
301
- **Example:**
302
- ```typescript
303
- const tableData: TableData = {
304
- // ... other config
305
- tabs: {
306
- method: (tab, event) => {
307
- if (tab.label === 'Active') {
308
- this.loadActiveProducts();
309
- } else if (tab.label === 'Inactive') {
310
- this.loadInactiveProducts();
311
- }
312
- },
313
- tabsData: [
314
- { label: 'Active', counter: 42, counterClass: 'bg-green-500' },
315
- { label: 'Inactive', counter: 8, counterClass: 'bg-gray-500' },
316
- { label: 'All' }
317
- ]
318
- }
319
- };
320
- ```
321
-
322
- ---
323
-
324
- ### FilterableOption
325
-
326
- Predefined filter buttons that users can click to apply common filters.
327
-
328
- | Property | Type | Required | Description |
329
- |----------|------|----------|-------------|
330
- | `title` | `string` | ✅ | Label for the filter group. |
331
- | `items` | `FilterItem[]` | ✅ | Array of individual filter options. |
332
-
333
- **FilterItem Properties:**
334
- - `property` (string): Property to filter on
335
- - `value` (string \| boolean): Value to filter by
336
- - `label` (string): Text displayed on the filter button
337
-
338
- **Example:**
339
- ```typescript
340
- const filterableOptions: FilterableOption[] = [
341
- {
342
- title: 'Status',
343
- items: [
344
- { property: 'status', value: 'active', label: 'Active' },
345
- { property: 'status', value: 'pending', label: 'Pending' },
346
- { property: 'status', value: 'archived', label: 'Archived' }
347
- ]
348
- },
349
- {
350
- title: 'Category',
351
- items: [
352
- { property: 'category', value: 'electronics', label: 'Electronics' },
353
- { property: 'category', value: 'clothing', label: 'Clothing' }
354
- ]
355
- }
356
- ];
357
- ```
358
-
359
- ---
360
-
361
- ### Condition
362
-
363
- Firestore `where` clauses that are always applied to queries, before any user filters.
364
-
365
- | Property | Type | Required | Description |
366
- |----------|------|----------|-------------|
367
- | `operator` | `WhereFilterOp` | ✅ | Firestore comparison operator: `'=='`, `'!='`, `'<'`, `'<='`, `'>'`, `'>='`, `'array-contains'`, `'in'`, `'array-contains-any'`, `'not-in'`. |
368
- | `firestoreProperty` | `string` | ✅ | Name of the field in Firestore to filter on. |
369
- | `dashProperty` | `string \| string[]` | ✅ | Value or array of values to compare against. Can be a static value or a property name from your component. |
370
-
371
- **Example:**
372
- ```typescript
373
- const conditions: Condition[] = [
374
- {
375
- operator: '==',
376
- firestoreProperty: 'tenantId',
377
- dashProperty: this.currentTenantId
378
- },
379
- {
380
- operator: '>=',
381
- firestoreProperty: 'createdAt',
382
- dashProperty: this.startDate
383
- },
384
- {
385
- operator: 'in',
386
- firestoreProperty: 'status',
387
- dashProperty: ['active', 'pending']
388
- }
389
- ];
390
- ```
391
-
392
- ---
393
-
394
- ### Arrange
395
-
396
- Internal interface used for managing sort and filter state. Automatically handled by the table component.
397
-
398
- | Property | Type | Description |
399
- |----------|------|-------------|
400
- | `filters` | `FilterState[]` | Array of active filters per column. |
401
- | `sortBy` | `{field: string, order: OrderByDirection}` | Current sort configuration. |
402
- | `elementId` | `{property: string, value: string}` | Optional identifier for highlighting a specific row. |
403
-
404
- **FilterState Properties:**
405
- - `arrange` ('ascending' \| 'descending' \| 'filter' \| 'filterByDate' \| 'equals' \| ''): Type of filter/sort applied
406
- - `filter` ({property: string, filtering: string}): Text filter configuration
407
- - `dateFilter` ({initial: Date, final: Date}): Date range filter
408
-
409
- ---
410
-
411
- ### Image
412
-
413
- Configuration for displaying images in table cells.
414
-
415
- | Property | Type | Required | Description |
416
- |----------|------|----------|-------------|
417
- | `class` | `string` | ✅ | CSS classes for styling the image element. |
418
- | `path` | `string` | ❌ | Property name containing the image path/URL in the row data. |
419
- | `url` | `boolean` | ❌ | If `true`, treats the value as a complete URL. Otherwise, constructs path from storage. |
420
- | `default` | `string` | ❌ | Default image URL if the actual image is missing or fails to load. |
421
-
422
- **Example:**
423
- ```typescript
424
- const column: Column = {
425
- property: 'avatar',
426
- title: 'Photo',
427
- image: {
428
- class: 'w-10 h-10 rounded-full object-cover',
429
- path: 'photoURL',
430
- url: true,
431
- default: '/assets/default-avatar.png'
432
- }
433
- };
434
- ```
435
-
436
- ---
437
-
438
- ### Data Export (Download)
439
-
440
- The library provides a flexible export system that allows you to download table data with the currently applied filters, sorting, and conditions.
441
-
442
- #### How It Works
443
-
444
- 1. **Pass a download handler** to the table component via the `[downloadTable]` input
445
- 2. **User clicks** the download button in the table UI
446
- 3. **Library calls your handler** with current `arrange` (filters/sort state) and `conditions` (where clauses)
447
- 4. **Your handler** fetches all data using `TableService.getItemsData()`, transforms it, and generates the file
448
-
449
- #### Basic Example
450
-
451
- ```typescript
452
- import { TableService } from '@adryanmmm/ng-firebase-table-kxp';
453
- import * as XLSX from 'xlsx';
454
- import moment from 'moment';
455
-
456
- export class UsersComponent {
457
- constructor(private tableService: TableService) {}
458
-
459
- // Pass this method to [downloadTable]
460
- downloadTable = async (arrange: Arrange, conditions: Condition[]) => {
461
- // 1. Fetch all data with current filters
462
- const rawData = await this.tableService.getItemsData(
463
- 'users', // collection name
464
- arrange, // current sort/filter state
465
- conditions // optional where conditions
466
- );
467
-
468
- // 2. Transform data to desired format
469
- const xlsxData = rawData.map(user => ({
470
- 'Name': user.fullName,
471
- 'Email': user.email,
472
- 'Phone': user.phoneNumber,
473
- 'Created': new Date(user.createdAt.seconds * 1000).toLocaleDateString()
474
- }));
475
-
476
- // 3. Generate Excel file
477
- const worksheet = XLSX.utils.json_to_sheet(xlsxData);
478
- const workbook = XLSX.utils.book_new();
479
- XLSX.utils.book_append_sheet(workbook, worksheet, 'Users');
480
-
481
- // 4. Download file
482
- const fileName = `Users_${moment().format('DD-MM-YYYY-HH-mm-ss')}.xlsx`;
483
- XLSX.writeFile(workbook, fileName);
484
- }
485
- }
486
- ```
487
-
488
- #### Advanced Example with Totals
489
-
490
- ```typescript
491
- downloadOrdersTable = async (arrange: Arrange, conditions: Condition[]) => {
492
- const rawData = await this.tableService.getItemsData(
493
- 'orders',
494
- arrange,
495
- conditions
496
- );
497
-
498
- let totalAmount = 0;
499
-
500
- // Map data and calculate totals
501
- const xlsxData = rawData.map(order => {
502
- totalAmount += order.amount;
503
- return {
504
- 'Order ID': order.id,
505
- 'Customer': order.customerName,
506
- 'Amount': order.amount,
507
- 'Date': new Date(order.createdAt.seconds * 1000).toLocaleDateString(),
508
- 'Status': order.status
509
- };
510
- });
511
-
512
- // Add totals row
513
- xlsxData.push({
514
- 'Order ID': '',
515
- 'Customer': '',
516
- 'Amount': totalAmount,
517
- 'Date': 'TOTAL',
518
- 'Status': ''
519
- });
520
-
521
- const worksheet = XLSX.utils.json_to_sheet(xlsxData);
522
- const workbook = XLSX.utils.book_new();
523
- XLSX.utils.book_append_sheet(workbook, worksheet, 'Orders');
524
-
525
- const fileName = `Orders_${moment().format('DD-MM-YYYY-HH-mm-ss')}.xlsx`;
526
- XLSX.writeFile(workbook, fileName);
527
- }
528
- ```
529
-
530
- #### Filtering Data Before Export
531
-
532
- You can apply additional client-side filters based on user permissions:
533
-
534
- ```typescript
535
- downloadTable = async (arrange: Arrange, conditions: Condition[]) => {
536
- const rawData = await this.tableService.getItemsData('orders', arrange, conditions);
537
-
538
- // Filter based on user permissions
539
- const filteredData = rawData.filter(order => {
540
- if (this.currentUser.role === 'Admin') return true;
541
- if (this.currentUser.storeId === order.storeId) return true;
542
- return false;
543
- });
544
-
545
- // ... continue with export
546
- }
547
- ```
548
-
549
- #### Using Custom Pipes for Formatting
550
-
551
- Apply Angular pipes to format data in the export:
552
-
553
- ```typescript
554
- import { DatePipe, CurrencyPipe } from '@angular/common';
555
-
556
- downloadTable = async (arrange: Arrange, conditions: Condition[]) => {
557
- const rawData = await this.tableService.getItemsData('products', arrange);
558
-
559
- const datePipe = new DatePipe('en-US');
560
- const currencyPipe = new CurrencyPipe('en-US');
561
-
562
- const xlsxData = rawData.map(product => ({
563
- 'Product': product.name,
564
- 'Price': currencyPipe.transform(product.price),
565
- 'Created': datePipe.transform(product.createdAt.toDate(), 'short'),
566
- 'Status': product.active ? 'Active' : 'Inactive'
567
- }));
568
-
569
- // ... continue with export
570
- }
571
- ```
572
-
573
- #### CSV vs XLSX Format
574
-
575
- ```typescript
576
- // Export as XLSX (default)
577
- XLSX.writeFile(workbook, 'data.xlsx');
578
-
579
- // Export as CSV
580
- XLSX.writeFile(workbook, 'data.csv', { bookType: 'csv', type: 'binary' });
581
- ```
582
-
583
- #### Important Notes
584
-
585
- - The `download` property in `TableData` must be `true` to show the download button
586
- - `TableService.getItemsData()` returns **all** data matching filters (ignores pagination)
587
- - The method is `async` because it fetches data from Firestore
588
- - You control the final file format, column names, and data transformations
589
- - The library provides the current filter/sort state; you handle the actual file generation
590
-
591
- ## Powered by KXP Tech
592
-
593
- Firebase Table KXP was designed inside **KXP Tech** to accelerate data-heavy application development. If you are looking for expert consultancy on Firebase, Angular, or enterprise dashboards, reach out at [kxptech.com](https://kxptech.com).
594
-
595
- ## Contributing & License
596
-
597
- Contributions, feedback, and feature requests are welcome. Fork the repository, open a pull request, or start a discussion on GitHub.
598
-
599
- This project is released under the MIT License. See the [LICENSE](LICENSE) file for details.
600
-
601
- ## Support the Project
602
-
1
+ # NG Firebase Table KXP
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+
5
+ NG Firebase Table KXP is a production-ready Angular data table created by **KXP Tech** to power enterprise dashboards backed by Firebase Firestore. It wraps complex Firestore querying, pagination, filtering, and material design styling into a plug-and-play module so teams can ship data-rich experiences faster.
6
+
7
+ ## Table of Contents
8
+ - [NG Firebase Table KXP](#ng-firebase-table-kxp)
9
+ - [Table of Contents](#table-of-contents)
10
+ - [Why Firebase Table KXP](#why-firebase-table-kxp)
11
+ - [Core Features](#core-features)
12
+ - [Installation \& Setup](#installation--setup)
13
+ - [Configuration Overview](#configuration-overview)
14
+ - [API Reference](#api-reference)
15
+ - [TableData](#tabledata)
16
+ - [Column](#column)
17
+ - [Tab \& TabData](#tab--tabdata)
18
+ - [FilterableOption](#filterableoption)
19
+ - [Condition](#condition)
20
+ - [Arrange](#arrange)
21
+ - [Image](#image)
22
+ - [Data Export (Download)](#data-export-download)
23
+ - [How It Works](#how-it-works)
24
+ - [Basic Example](#basic-example)
25
+ - [Advanced Example with Totals](#advanced-example-with-totals)
26
+ - [Filtering Data Before Export](#filtering-data-before-export)
27
+ - [Using Custom Pipes for Formatting](#using-custom-pipes-for-formatting)
28
+ - [CSV vs XLSX Format](#csv-vs-xlsx-format)
29
+ - [Important Notes](#important-notes)
30
+ - [Powered by KXP Tech](#powered-by-kxp-tech)
31
+ - [Contributing \& License](#contributing--license)
32
+ - [Support the Project](#support-the-project)
33
+
34
+ ## Why Firebase Table KXP
35
+
36
+ - Built and battle-tested at **KXP Tech** across projects with thousands of active users
37
+ - Eliminates weeks of custom table work; drop it in and configure
38
+ - Handles Firestore quirks (pagination, missing indexes, fallbacks) automatically
39
+ - Provides a strongly typed API that scales with project complexity
40
+ - Plays nicely with Angular Material, Firebase, and Angular Fire
41
+
42
+ ## Core Features
43
+
44
+ - Full Firestore integration with server-side and client-side pagination
45
+ - Text, date, and custom filters with automatic index fallback when needed
46
+ - Configurable columns for relations, custom renderers, actions, and exports
47
+ - Built-in CSV export hooks and actionable buttons per row
48
+ - Pagination, sorting, and filter state shared between tabs
49
+ - Optional color theming, tabbed datasets, and custom navigation per row
50
+ - Works seamlessly with Angular 15, Angular Material, Angular Fire, and RxJS
51
+
52
+ ## Installation & Setup
53
+
54
+ ```bash
55
+ npm install @adryanmmm/ng-firebase-table-kxp
56
+ ```
57
+
58
+ The host application should already depend on:
59
+
60
+ - Angular 15 packages: `@angular/common`, `@angular/core`, `@angular/forms`, `@angular/router`, `@angular/cdk`, `@angular/material`
61
+ - Firebase stack: `@angular/fire` (7.x) and `firebase` (9.x)
62
+ - Utilities: `ngx-toastr` (16.x), `moment` (2.29.x), `ngx-mask` (15.x)
63
+
64
+
65
+ 1. **Import the module**
66
+ ```typescript
67
+ import { NgFirebaseTableKxpModule } from '@adryanmmm/ng-firebase-table-kxp';
68
+
69
+ @NgModule({
70
+ imports: [
71
+ BrowserModule,
72
+ BrowserAnimationsModule,
73
+ AngularFireModule.initializeApp(environment.firebase),
74
+ AngularFirestoreModule,
75
+ ToastrModule.forRoot(),
76
+ NgxMaskModule.forRoot(),
77
+ NgFirebaseTableKxpModule
78
+ ]
79
+ })
80
+ export class AppModule {}
81
+ ```
82
+
83
+ 2. **Drop the component**
84
+ ```typescript
85
+ @Component({
86
+ selector: 'app-users',
87
+ template: `
88
+ <lib-table
89
+ [data]="table"
90
+ [downloadTable]="downloadTable">
91
+ </lib-table>
92
+ `
93
+ })
94
+ export class UsersComponent {
95
+ table: TableData = {
96
+ name: 'users',
97
+ collection: 'users',
98
+ collectionRef: this.firestore.collection('users').ref,
99
+ pagination: true,
100
+ download: true,
101
+ displayedColumns: [
102
+ { property: 'name', title: 'Name', isFilterable: true, isSortable: true },
103
+ { property: 'email', title: 'Email', isFilterable: true }
104
+ ]
105
+ };
106
+
107
+ downloadTable = (arrange: any, conditions: any[]) => {
108
+ // export logic
109
+ };
110
+
111
+ constructor(private firestore: AngularFirestore) {}
112
+ }
113
+ ```
114
+
115
+ 3. **Make sure global styles include**
116
+ ```scss
117
+ @import '@angular/material/prebuilt-themes/indigo-pink.css';
118
+ @import 'ngx-toastr/toastr';
119
+ ```
120
+
121
+ ## Configuration Overview
122
+
123
+ - `TableData` defines the table behavior: source collection, pagination, sorting, tabs, exported totals, and custom navigation.
124
+ - `Column` describes each displayed column: filters, relations, custom templates, icon actions, download buttons, and data formatting.
125
+ - `Condition` lets you predefine Firestore `where` clauses applied before any user filters.
126
+ - Optional hooks like `filterFn`, `actionButton`, and `tabs` enable richer UX without duplicating logic.
127
+ - Automatic client-side fallback when Firestore reports missing composite indexes, plus logging of the index link to a `missingIndexes` collection.
128
+ - Support for joining related collections, counting related documents per row, and managing dialog-driven actions via `iconClass` or `actionButton` handlers.
129
+ - Compatible with SSR, lazy-loaded modules, and Angular standalone components.
130
+
131
+ ## API Reference
132
+
133
+ This section provides detailed documentation for all configuration interfaces available in the library.
134
+
135
+ ### TableData
136
+
137
+ The main configuration object that defines the entire table behavior.
138
+
139
+ | Property | Type | Required | Description |
140
+ |----------|------|----------|-------------|
141
+ | `name` | `string` | ✅ | Unique identifier for the table. Used internally for caching and state management. |
142
+ | `collection` | `string` | ✅ | Name of the Firestore collection to query. |
143
+ | `collectionRef` | `CollectionReference` | ✅ | Reference to the Firestore collection. Use `firestore.collection('name').ref`. |
144
+ | `displayedColumns` | `Column[]` | ✅ | Array of column definitions. Defines what data to display and how. See [Column](#column). |
145
+ | `pagination` | `boolean` | ✅ | Enable or disable pagination. When `true`, data is loaded in batches. |
146
+ | `download` | `boolean` | ✅ | Enable or disable the export/download functionality. |
147
+ | `sortBy` | `{field: string, order: OrderByDirection}` | ❌ | Default sorting configuration. `order` can be `'asc'` or `'desc'`. |
148
+ | `conditions` | `Condition[]` | ❌ | Array of Firestore where clauses applied before user filters. See [Condition](#condition). |
149
+ | `filterFn` | `(item: any) => boolean` | ❌ | Custom client-side filter function. Applied after Firestore queries. |
150
+ | `filterableOptions` | `FilterableOption[]` | ❌ | Predefined filter options displayed as buttons. See [FilterableOption](#filterableoption). |
151
+ | `url` | `string` | ❌ | Base URL for row navigation. Row clicks navigate to `{url}/{row.id}`. |
152
+ | `isNotClickable` | `boolean` | ❌ | When `true`, disables row click navigation. |
153
+ | `color` | `{bg: string, text: string}` | ❌ | CSS classes for custom table theming. `bg` for background, `text` for text color. |
154
+ | `totalRef` | `{ref: DocumentReference, field: string}[]` | ❌ | References to documents containing total counts. Displayed in the table footer. |
155
+ | `actionButton` | `ActionButton` | ❌ | Configuration for a primary action button (e.g., "Add New"). |
156
+ | `tabs` | `Tab` | ❌ | Configuration for tabbed data views. See [Tab & TabData](#tab--tabdata). |
157
+
158
+ **ActionButton Properties:**
159
+ - `label` (string): Button text
160
+ - `routerLink` (string): Navigation route
161
+ - `icon` (string): CSS class for icon (e.g., FontAwesome)
162
+ - `colorClass` (string): CSS class for button styling
163
+ - `method` ((row, event) => any): Custom click handler
164
+ - `condition` ((row) => boolean): Function to conditionally show the button
165
+
166
+ **Example:**
167
+ ```typescript
168
+ const tableData: TableData = {
169
+ name: 'products',
170
+ collection: 'products',
171
+ collectionRef: this.firestore.collection('products').ref,
172
+ pagination: true,
173
+ download: true,
174
+ sortBy: { field: 'createdAt', order: 'desc' },
175
+ conditions: [
176
+ { operator: '==', firestoreProperty: 'active', dashProperty: true }
177
+ ],
178
+ displayedColumns: [/* ... */],
179
+ actionButton: {
180
+ label: 'Add Product',
181
+ routerLink: '/products/new',
182
+ icon: 'fa fa-plus',
183
+ colorClass: 'bg-blue-500'
184
+ }
185
+ };
186
+ ```
187
+
188
+ ---
189
+
190
+ ### Column
191
+
192
+ Defines how each column is displayed, filtered, and interacted with.
193
+
194
+ | Property | Type | Required | Description |
195
+ |----------|------|----------|-------------|
196
+ | `property` | `string` | ✅ | Name of the property in the data object to display. |
197
+ | `title` | `string` | ❌ | Column header text. Defaults to `property` if not provided. |
198
+ | `charLimit` | `number` | ❌ | Maximum characters to display. Shows tooltip with full text on hover. |
199
+ | `pipe` | `PipeTransform` | ❌ | Angular pipe for formatting values (e.g., `DatePipe`, `CurrencyPipe`). |
200
+ | `calculateValue` | `(row: any) => any` | ❌ | Function to compute the display value from the row data. |
201
+ | `isSortable` | `boolean` | ❌ | Enable sorting for this column. |
202
+ | `isFilterable` | `boolean` | ❌ | Enable text-based filtering for this column. |
203
+ | `isFilterableByDate` | `boolean` | ❌ | Enable date range filtering. Shows date picker inputs. |
204
+ | `filterPredicates` | `string[]` | ❌ | Additional properties to include when filtering this column. |
205
+ | `hasLink` | `boolean \| string` | ❌ | Make the cell value a clickable link. If string, uses as the navigation path. |
206
+ | `hasDownload` | `boolean \| string` | ❌ | Add download icon to the cell. If string, uses as the download URL property. |
207
+ | `arrayField` | `string` | ❌ | When data is an array of objects, specify which field to display. |
208
+ | `method` | `(row: any, event?: any) => any` | ❌ | Custom click handler for the cell. |
209
+ | `image` | `Image` | ❌ | Configuration for displaying images. See [Image](#image). |
210
+ | `iconClass` | `IconClass[]` | ❌ | Array of icon/button configurations for action buttons in the cell. |
211
+ | `relation` | `Relation` | ❌ | Configuration for joining related collection data. |
212
+ | `queryLength` | `QueryLength` | ❌ | Configuration for counting related documents. |
213
+
214
+ **IconClass Properties:**
215
+ - `class` (string): CSS classes for the icon/button
216
+ - `text` (string): Text to display alongside icon
217
+ - `condition` ((row) => any): Function to conditionally show the icon
218
+ - `buttonMethod` ((row, event) => any): Click handler for the icon
219
+
220
+ **Relation Properties:**
221
+ - `collection` (string): Name of the related collection
222
+ - `property` (string): Property in current row containing the related document ID
223
+ - `newProperty` (string): Property from related document to fetch and display
224
+
225
+ **QueryLength Properties:**
226
+ - `collection` (string): Collection to count documents from
227
+ - `property` (string): Field to match in the query
228
+ - `operator` (WhereFilterOp): Firestore operator (e.g., `'=='`, `'>'`)
229
+ - `value` (string): Property from current row to use as query value
230
+
231
+ **Example:**
232
+ ```typescript
233
+ const columns: Column[] = [
234
+ {
235
+ property: 'name',
236
+ title: 'Product Name',
237
+ isFilterable: true,
238
+ isSortable: true,
239
+ charLimit: 50
240
+ },
241
+ {
242
+ property: 'price',
243
+ title: 'Price',
244
+ pipe: new CurrencyPipe('en-US'),
245
+ isSortable: true
246
+ },
247
+ {
248
+ property: 'createdAt',
249
+ title: 'Created',
250
+ pipe: new DatePipe('en-US'),
251
+ isFilterableByDate: true
252
+ },
253
+ {
254
+ property: 'categoryName',
255
+ title: 'Category',
256
+ relation: {
257
+ collection: 'categories',
258
+ property: 'categoryId',
259
+ newProperty: 'name'
260
+ }
261
+ },
262
+ {
263
+ property: 'actions',
264
+ title: 'Actions',
265
+ iconClass: [
266
+ {
267
+ class: 'fa fa-edit text-blue-500 cursor-pointer',
268
+ buttonMethod: (row) => this.editProduct(row)
269
+ },
270
+ {
271
+ class: 'fa fa-trash text-red-500 cursor-pointer',
272
+ buttonMethod: (row) => this.deleteProduct(row),
273
+ condition: (row) => row.canDelete === true
274
+ }
275
+ ]
276
+ }
277
+ ];
278
+ ```
279
+
280
+ ---
281
+
282
+ ### Tab & TabData
283
+
284
+ Configure tabbed views for switching between different data sets within the same table.
285
+
286
+ **Tab Interface:**
287
+
288
+ | Property | Type | Required | Description |
289
+ |----------|------|----------|-------------|
290
+ | `method` | `(tab: any, event?: any) => any` | ✅ | Function called when a tab is clicked. Use to switch data or apply filters. |
291
+ | `tabsData` | `TabData[]` | ✅ | Array of tab configurations. |
292
+
293
+ **TabData Interface:**
294
+
295
+ | Property | Type | Required | Description |
296
+ |----------|------|----------|-------------|
297
+ | `label` | `string` | ✅ | Text displayed on the tab. |
298
+ | `counter` | `number` | ❌ | Optional counter badge displayed next to the label. |
299
+ | `counterClass` | `string` | ❌ | CSS class for styling the counter badge. |
300
+
301
+ **Example:**
302
+ ```typescript
303
+ const tableData: TableData = {
304
+ // ... other config
305
+ tabs: {
306
+ method: (tab, event) => {
307
+ if (tab.label === 'Active') {
308
+ this.loadActiveProducts();
309
+ } else if (tab.label === 'Inactive') {
310
+ this.loadInactiveProducts();
311
+ }
312
+ },
313
+ tabsData: [
314
+ { label: 'Active', counter: 42, counterClass: 'bg-green-500' },
315
+ { label: 'Inactive', counter: 8, counterClass: 'bg-gray-500' },
316
+ { label: 'All' }
317
+ ]
318
+ }
319
+ };
320
+ ```
321
+
322
+ ---
323
+
324
+ ### FilterableOption
325
+
326
+ Predefined filter buttons that users can click to apply common filters.
327
+
328
+ | Property | Type | Required | Description |
329
+ |----------|------|----------|-------------|
330
+ | `title` | `string` | ✅ | Label for the filter group. |
331
+ | `items` | `FilterItem[]` | ✅ | Array of individual filter options. |
332
+
333
+ **FilterItem Properties:**
334
+ - `property` (string): Property to filter on
335
+ - `value` (string \| boolean): Value to filter by
336
+ - `label` (string): Text displayed on the filter button
337
+
338
+ **Example:**
339
+ ```typescript
340
+ const filterableOptions: FilterableOption[] = [
341
+ {
342
+ title: 'Status',
343
+ items: [
344
+ { property: 'status', value: 'active', label: 'Active' },
345
+ { property: 'status', value: 'pending', label: 'Pending' },
346
+ { property: 'status', value: 'archived', label: 'Archived' }
347
+ ]
348
+ },
349
+ {
350
+ title: 'Category',
351
+ items: [
352
+ { property: 'category', value: 'electronics', label: 'Electronics' },
353
+ { property: 'category', value: 'clothing', label: 'Clothing' }
354
+ ]
355
+ }
356
+ ];
357
+ ```
358
+
359
+ ---
360
+
361
+ ### Condition
362
+
363
+ Firestore `where` clauses that are always applied to queries, before any user filters.
364
+
365
+ | Property | Type | Required | Description |
366
+ |----------|------|----------|-------------|
367
+ | `operator` | `WhereFilterOp` | ✅ | Firestore comparison operator: `'=='`, `'!='`, `'<'`, `'<='`, `'>'`, `'>='`, `'array-contains'`, `'in'`, `'array-contains-any'`, `'not-in'`. |
368
+ | `firestoreProperty` | `string` | ✅ | Name of the field in Firestore to filter on. |
369
+ | `dashProperty` | `string \| string[]` | ✅ | Value or array of values to compare against. Can be a static value or a property name from your component. |
370
+
371
+ **Example:**
372
+ ```typescript
373
+ const conditions: Condition[] = [
374
+ {
375
+ operator: '==',
376
+ firestoreProperty: 'tenantId',
377
+ dashProperty: this.currentTenantId
378
+ },
379
+ {
380
+ operator: '>=',
381
+ firestoreProperty: 'createdAt',
382
+ dashProperty: this.startDate
383
+ },
384
+ {
385
+ operator: 'in',
386
+ firestoreProperty: 'status',
387
+ dashProperty: ['active', 'pending']
388
+ }
389
+ ];
390
+ ```
391
+
392
+ ---
393
+
394
+ ### Arrange
395
+
396
+ Internal interface used for managing sort and filter state. Automatically handled by the table component.
397
+
398
+ | Property | Type | Description |
399
+ |----------|------|-------------|
400
+ | `filters` | `FilterState[]` | Array of active filters per column. |
401
+ | `sortBy` | `{field: string, order: OrderByDirection}` | Current sort configuration. |
402
+ | `elementId` | `{property: string, value: string}` | Optional identifier for highlighting a specific row. |
403
+
404
+ **FilterState Properties:**
405
+ - `arrange` ('ascending' \| 'descending' \| 'filter' \| 'filterByDate' \| 'equals' \| ''): Type of filter/sort applied
406
+ - `filter` ({property: string, filtering: string}): Text filter configuration
407
+ - `dateFilter` ({initial: Date, final: Date}): Date range filter
408
+
409
+ ---
410
+
411
+ ### Image
412
+
413
+ Configuration for displaying images in table cells.
414
+
415
+ | Property | Type | Required | Description |
416
+ |----------|------|----------|-------------|
417
+ | `class` | `string` | ✅ | CSS classes for styling the image element. |
418
+ | `path` | `string` | ❌ | Property name containing the image path/URL in the row data. |
419
+ | `url` | `boolean` | ❌ | If `true`, treats the value as a complete URL. Otherwise, constructs path from storage. |
420
+ | `default` | `string` | ❌ | Default image URL if the actual image is missing or fails to load. |
421
+
422
+ **Example:**
423
+ ```typescript
424
+ const column: Column = {
425
+ property: 'avatar',
426
+ title: 'Photo',
427
+ image: {
428
+ class: 'w-10 h-10 rounded-full object-cover',
429
+ path: 'photoURL',
430
+ url: true,
431
+ default: '/assets/default-avatar.png'
432
+ }
433
+ };
434
+ ```
435
+
436
+ ---
437
+
438
+ ### Data Export (Download)
439
+
440
+ The library provides a flexible export system that allows you to download table data with the currently applied filters, sorting, and conditions.
441
+
442
+ #### How It Works
443
+
444
+ 1. **Pass a download handler** to the table component via the `[downloadTable]` input
445
+ 2. **User clicks** the download button in the table UI
446
+ 3. **Library calls your handler** with current `arrange` (filters/sort state) and `conditions` (where clauses)
447
+ 4. **Your handler** fetches all data using `TableService.getItemsData()`, transforms it, and generates the file
448
+
449
+ #### Basic Example
450
+
451
+ ```typescript
452
+ import { TableService } from '@adryanmmm/ng-firebase-table-kxp';
453
+ import * as XLSX from 'xlsx';
454
+ import moment from 'moment';
455
+
456
+ export class UsersComponent {
457
+ constructor(private tableService: TableService) {}
458
+
459
+ // Pass this method to [downloadTable]
460
+ downloadTable = async (arrange: Arrange, conditions: Condition[]) => {
461
+ // 1. Fetch all data with current filters
462
+ const rawData = await this.tableService.getItemsData(
463
+ 'users', // collection name
464
+ arrange, // current sort/filter state
465
+ conditions // optional where conditions
466
+ );
467
+
468
+ // 2. Transform data to desired format
469
+ const xlsxData = rawData.map(user => ({
470
+ 'Name': user.fullName,
471
+ 'Email': user.email,
472
+ 'Phone': user.phoneNumber,
473
+ 'Created': new Date(user.createdAt.seconds * 1000).toLocaleDateString()
474
+ }));
475
+
476
+ // 3. Generate Excel file
477
+ const worksheet = XLSX.utils.json_to_sheet(xlsxData);
478
+ const workbook = XLSX.utils.book_new();
479
+ XLSX.utils.book_append_sheet(workbook, worksheet, 'Users');
480
+
481
+ // 4. Download file
482
+ const fileName = `Users_${moment().format('DD-MM-YYYY-HH-mm-ss')}.xlsx`;
483
+ XLSX.writeFile(workbook, fileName);
484
+ }
485
+ }
486
+ ```
487
+
488
+ #### Advanced Example with Totals
489
+
490
+ ```typescript
491
+ downloadOrdersTable = async (arrange: Arrange, conditions: Condition[]) => {
492
+ const rawData = await this.tableService.getItemsData(
493
+ 'orders',
494
+ arrange,
495
+ conditions
496
+ );
497
+
498
+ let totalAmount = 0;
499
+
500
+ // Map data and calculate totals
501
+ const xlsxData = rawData.map(order => {
502
+ totalAmount += order.amount;
503
+ return {
504
+ 'Order ID': order.id,
505
+ 'Customer': order.customerName,
506
+ 'Amount': order.amount,
507
+ 'Date': new Date(order.createdAt.seconds * 1000).toLocaleDateString(),
508
+ 'Status': order.status
509
+ };
510
+ });
511
+
512
+ // Add totals row
513
+ xlsxData.push({
514
+ 'Order ID': '',
515
+ 'Customer': '',
516
+ 'Amount': totalAmount,
517
+ 'Date': 'TOTAL',
518
+ 'Status': ''
519
+ });
520
+
521
+ const worksheet = XLSX.utils.json_to_sheet(xlsxData);
522
+ const workbook = XLSX.utils.book_new();
523
+ XLSX.utils.book_append_sheet(workbook, worksheet, 'Orders');
524
+
525
+ const fileName = `Orders_${moment().format('DD-MM-YYYY-HH-mm-ss')}.xlsx`;
526
+ XLSX.writeFile(workbook, fileName);
527
+ }
528
+ ```
529
+
530
+ #### Filtering Data Before Export
531
+
532
+ You can apply additional client-side filters based on user permissions:
533
+
534
+ ```typescript
535
+ downloadTable = async (arrange: Arrange, conditions: Condition[]) => {
536
+ const rawData = await this.tableService.getItemsData('orders', arrange, conditions);
537
+
538
+ // Filter based on user permissions
539
+ const filteredData = rawData.filter(order => {
540
+ if (this.currentUser.role === 'Admin') return true;
541
+ if (this.currentUser.storeId === order.storeId) return true;
542
+ return false;
543
+ });
544
+
545
+ // ... continue with export
546
+ }
547
+ ```
548
+
549
+ #### Using Custom Pipes for Formatting
550
+
551
+ Apply Angular pipes to format data in the export:
552
+
553
+ ```typescript
554
+ import { DatePipe, CurrencyPipe } from '@angular/common';
555
+
556
+ downloadTable = async (arrange: Arrange, conditions: Condition[]) => {
557
+ const rawData = await this.tableService.getItemsData('products', arrange);
558
+
559
+ const datePipe = new DatePipe('en-US');
560
+ const currencyPipe = new CurrencyPipe('en-US');
561
+
562
+ const xlsxData = rawData.map(product => ({
563
+ 'Product': product.name,
564
+ 'Price': currencyPipe.transform(product.price),
565
+ 'Created': datePipe.transform(product.createdAt.toDate(), 'short'),
566
+ 'Status': product.active ? 'Active' : 'Inactive'
567
+ }));
568
+
569
+ // ... continue with export
570
+ }
571
+ ```
572
+
573
+ #### CSV vs XLSX Format
574
+
575
+ ```typescript
576
+ // Export as XLSX (default)
577
+ XLSX.writeFile(workbook, 'data.xlsx');
578
+
579
+ // Export as CSV
580
+ XLSX.writeFile(workbook, 'data.csv', { bookType: 'csv', type: 'binary' });
581
+ ```
582
+
583
+ #### Important Notes
584
+
585
+ - The `download` property in `TableData` must be `true` to show the download button
586
+ - `TableService.getItemsData()` returns **all** data matching filters (ignores pagination)
587
+ - The method is `async` because it fetches data from Firestore
588
+ - You control the final file format, column names, and data transformations
589
+ - The library provides the current filter/sort state; you handle the actual file generation
590
+
591
+ ## Powered by KXP Tech
592
+
593
+ Firebase Table KXP was designed inside **KXP Tech** to accelerate data-heavy application development. If you are looking for expert consultancy on Firebase, Angular, or enterprise dashboards, reach out at [kxptech.com](https://kxptech.com).
594
+
595
+ ## Contributing & License
596
+
597
+ Contributions, feedback, and feature requests are welcome. Fork the repository, open a pull request, or start a discussion on GitHub.
598
+
599
+ This project is released under the MIT License. See the [LICENSE](LICENSE) file for details.
600
+
601
+ ## Support the Project
602
+
603
603
  If this library saves you time, please star the repository and share it with your team.