@mysteryinfosolutions/api-core 1.7.13 → 1.9.0

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,63 +1,960 @@
1
- # ApiCore
1
+ # @mysteryinfosolutions/api-core
2
2
 
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.0.0.
3
+ A comprehensive Angular library providing robust base services, state management, and utilities for building data-driven applications with RESTful APIs.
4
4
 
5
- ## Code scaffolding
5
+ [![Angular](https://img.shields.io/badge/Angular-20.0.0-red)](https://angular.io/)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.8.2-blue)](https://www.typescriptlang.org/)
7
+ [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
6
8
 
7
- Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
9
+ ## 📦 Installation
8
10
 
9
11
  ```bash
10
- ng generate component component-name
12
+ npm install @mysteryinfosolutions/api-core
11
13
  ```
12
14
 
13
- For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
15
+ ### Peer Dependencies
16
+
17
+ Ensure you have the following peer dependencies installed:
14
18
 
15
19
  ```bash
16
- ng generate --help
20
+ npm install @angular/common@^20.0.0 @angular/core@^20.0.0 rxjs@^7.0.0
17
21
  ```
18
22
 
19
- ## Building
23
+ ## 🎯 Features
20
24
 
21
- To build the library, run:
25
+ - **Generic CRUD Service** - Type-safe base service for REST operations
26
+ - ✅ **State Management** - Comprehensive reactive state management with RxJS
27
+ - ✅ **Query Builder** - Convert filters to query strings automatically
28
+ - ✅ **Pagination & Sorting** - Built-in support with metadata
29
+ - ✅ **Permission System** - Generate and manage resource permissions
30
+ - ✅ **Table Configuration** - Standardized table/grid configurations
31
+ - ✅ **Type-Safe Models** - Interfaces for API responses and filters
32
+ - ✅ **Loading States** - Context-aware loading state management
33
+ - ✅ **Tree-Shakable** - Optimized bundle size
22
34
 
23
- ```bash
24
- ng build api-core
35
+ ---
36
+
37
+ ## 🚀 Quick Start
38
+
39
+ ### 1. Basic Service Example
40
+
41
+ Create a service for your resource using `BaseService`:
42
+
43
+ ```typescript
44
+ import { Injectable } from '@angular/core';
45
+ import { HttpClient } from '@angular/common/http';
46
+ import { BaseService } from '@mysteryinfosolutions/api-core';
47
+
48
+ // Define your model
49
+ interface User {
50
+ id: number;
51
+ name: string;
52
+ email: string;
53
+ role: string;
54
+ createdAt: string;
55
+ }
56
+
57
+ // Define your filter (optional custom fields)
58
+ interface UserFilter {
59
+ role?: string;
60
+ active?: boolean;
61
+ page?: number;
62
+ pageLength?: number;
63
+ search?: string;
64
+ }
65
+
66
+ @Injectable({ providedIn: 'root' })
67
+ export class UserService extends BaseService<User, UserFilter> {
68
+ constructor(http: HttpClient) {
69
+ super(http, '/api/users');
70
+ }
71
+ }
25
72
  ```
26
73
 
27
- This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
74
+ ### 2. Use the Service in a Component
28
75
 
29
- ### Publishing the Library
76
+ ```typescript
77
+ import { Component, OnInit } from '@angular/core';
78
+ import { UserService } from './user.service';
30
79
 
31
- Once the project is built, you can publish your library by following these steps:
80
+ @Component({
81
+ selector: 'app-users',
82
+ template: `
83
+ <div *ngIf="users$ | async as users">
84
+ <div *ngFor="let user of users.data?.records">
85
+ {{ user.name }} - {{ user.email }}
86
+ </div>
87
+ </div>
88
+ `
89
+ })
90
+ export class UsersComponent implements OnInit {
91
+ users$ = this.userService.getAll({ page: 1, pageLength: 10 });
32
92
 
33
- 1. Navigate to the `dist` directory:
34
- ```bash
35
- cd dist/api-core
36
- ```
93
+ constructor(private userService: UserService) {}
37
94
 
38
- 2. Run the `npm publish` command to publish your library to the npm registry:
39
- ```bash
40
- npm publish
41
- ```
95
+ ngOnInit() {
96
+ // Load users on init
97
+ this.loadUsers();
98
+ }
42
99
 
43
- ## Running unit tests
100
+ loadUsers() {
101
+ this.userService.getAll({
102
+ page: 1,
103
+ pageLength: 25,
104
+ role: 'admin',
105
+ search: 'john'
106
+ }).subscribe(response => {
107
+ if (response.data) {
108
+ console.log('Users:', response.data.records);
109
+ console.log('Total:', response.data.pager.totalRecords);
110
+ }
111
+ });
112
+ }
44
113
 
45
- To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
114
+ createUser() {
115
+ this.userService.create({
116
+ name: 'John Doe',
117
+ email: 'john@example.com',
118
+ role: 'user'
119
+ }).subscribe(response => {
120
+ console.log('Created:', response.data);
121
+ });
122
+ }
46
123
 
47
- ```bash
48
- ng test
124
+ updateUser(id: number) {
125
+ this.userService.update(id, {
126
+ name: 'Jane Doe'
127
+ }).subscribe(response => {
128
+ console.log('Updated:', response.data);
129
+ });
130
+ }
131
+
132
+ deleteUser(id: number) {
133
+ // Soft delete (default)
134
+ this.userService.delete(id).subscribe(() => {
135
+ console.log('Deleted');
136
+ });
137
+
138
+ // Hard delete
139
+ this.userService.delete(id, 'hard').subscribe(() => {
140
+ console.log('Permanently deleted');
141
+ });
142
+ }
143
+ }
49
144
  ```
50
145
 
51
- ## Running end-to-end tests
146
+ ---
52
147
 
53
- For end-to-end (e2e) testing, run:
148
+ ## 📚 Core Components
54
149
 
55
- ```bash
56
- ng e2e
150
+ ### BaseService<T, TFilter, TCreate, TUpdate>
151
+
152
+ Generic HTTP service providing CRUD operations.
153
+
154
+ #### Type Parameters
155
+
156
+ | Parameter | Description | Default |
157
+ |-----------|-------------|---------|
158
+ | `T` | The full model type | Required |
159
+ | `TFilter` | Filter type (extends `Partial<T> & Filter`) | `Partial<T> & Filter` |
160
+ | `TCreate` | DTO for creating records | `Partial<T>` |
161
+ | `TUpdate` | DTO for updating records | `Partial<T>` |
162
+
163
+ #### Methods
164
+
165
+ | Method | Parameters | Returns | Description |
166
+ |--------|-----------|---------|-------------|
167
+ | `getAll()` | `filter?: TFilter` | `Observable<IResponse<IMultiresult<T>>>` | Fetch paginated list |
168
+ | `getDetails()` | `id: number` | `Observable<IResponse<T>>` | Fetch single record |
169
+ | `create()` | `data: TCreate` | `Observable<IResponse<T>>` | Create new record |
170
+ | `update()` | `id: number, data: TUpdate` | `Observable<IResponse<T>>` | Update existing record |
171
+ | `delete()` | `id: number, method?: 'soft' \| 'hard'` | `Observable<IResponse<any>>` | Delete record |
172
+
173
+ #### Example with Custom DTOs
174
+
175
+ ```typescript
176
+ interface User {
177
+ id: number;
178
+ name: string;
179
+ email: string;
180
+ password: string;
181
+ role: string;
182
+ }
183
+
184
+ interface CreateUserDto {
185
+ name: string;
186
+ email: string;
187
+ password: string;
188
+ role: string;
189
+ }
190
+
191
+ interface UpdateUserDto {
192
+ name?: string;
193
+ email?: string;
194
+ role?: string;
195
+ // Note: password excluded for updates
196
+ }
197
+
198
+ @Injectable({ providedIn: 'root' })
199
+ export class UserService extends BaseService<
200
+ User,
201
+ UserFilter,
202
+ CreateUserDto,
203
+ UpdateUserDto
204
+ > {
205
+ constructor(http: HttpClient) {
206
+ super(http, '/api/users');
207
+ }
208
+ }
209
+ ```
210
+
211
+ ---
212
+
213
+ ### BaseStateService<TRecord, TFilter>
214
+
215
+ Comprehensive reactive state management for data-driven features.
216
+
217
+ #### State Properties
218
+
219
+ | Observable | Type | Description |
220
+ |------------|------|-------------|
221
+ | `filter$` | `Observable<TFilter>` | Current filter state |
222
+ | `records$` | `Observable<TRecord[]>` | Current records |
223
+ | `pager$` | `Observable<IMultiresultMetaData \| null>` | Pagination metadata |
224
+ | `selected$` | `Observable<TRecord \| null>` | Selected record |
225
+ | `loading$` | `Observable<Record<string, boolean>>` | Loading states |
226
+ | `error$` | `Observable<string \| null>` | Error message |
227
+
228
+ #### Getters
229
+
230
+ | Getter | Type | Description |
231
+ |--------|------|-------------|
232
+ | `currentFilter` | `TFilter` | Current filter value |
233
+ | `currentRecords` | `TRecord[]` | Current records value |
234
+ | `selected` | `TRecord \| null` | Selected record value |
235
+
236
+ #### Key Methods
237
+
238
+ **Filter Management**
239
+ - `setFilter(update: Partial<TFilter>)` - Update filter
240
+ - `resetFilter(defaults?)` - Reset to defaults
241
+ - `setPage(page: number)` - Change page
242
+ - `setPageLength(pageLength: number)` - Change page size
243
+
244
+ **Sorting**
245
+ - `setSort(column: string, order?: 'ASC' | 'DESC')` - Add/update sort
246
+ - `removeSort(column: string)` - Remove specific sort
247
+ - `clearSort()` - Clear all sorting
248
+
249
+ **Records Management**
250
+ - `setRecords(records: TRecord[])` - Replace all records
251
+ - `appendRecords(records: TRecord[])` - Add records
252
+ - `removeRecordById(id, idKey?)` - Remove record
253
+ - `replaceRecord(updated: TRecord, idKey?)` - Update record
254
+
255
+ **Pagination**
256
+ - `setPager(pager: IMultiresultMetaData)` - Set pager
257
+ - `setApiResponse(response: IMultiresult<TRecord>)` - Set records + pager
258
+ - `hasMorePages()` - Check if more pages exist
259
+ - `hasPreviousPage()` - Check if previous page exists
260
+
261
+ **Selection**
262
+ - `select(record: TRecord)` - Select record
263
+ - `clearSelection()` - Clear selection
264
+ - `isSelected(record: TRecord, idKey?)` - Check if selected
265
+
266
+ **Loading States**
267
+ - `setLoading(key: string, value: boolean)` - Set loading state
268
+ - `isLoading$(key: string)` - Observable for specific loading key
269
+ - `clearLoading(key?)` - Clear loading state(s)
270
+
271
+ **Error Handling**
272
+ - `setError(error: string | null)` - Set error message
273
+
274
+ **Lifecycle**
275
+ - `reset()` - Reset all state
276
+ - `destroy()` - Reset and complete subscriptions
277
+ - `destroySubscriptions(subjects?)` - Complete specific subjects
278
+
279
+ #### Complete Example
280
+
281
+ ```typescript
282
+ import { Component, OnInit, OnDestroy } from '@angular/core';
283
+ import { BaseStateService } from '@mysteryinfosolutions/api-core';
284
+
285
+ interface Product {
286
+ id: number;
287
+ name: string;
288
+ price: number;
289
+ category: string;
290
+ }
291
+
292
+ interface ProductFilter {
293
+ category?: string;
294
+ minPrice?: number;
295
+ maxPrice?: number;
296
+ page?: number;
297
+ pageLength?: number;
298
+ search?: string;
299
+ }
300
+
301
+ @Component({
302
+ selector: 'app-products',
303
+ template: `
304
+ <div class="filters">
305
+ <input
306
+ [value]="(state.filter$ | async)?.search || ''"
307
+ (input)="onSearch($event)"
308
+ placeholder="Search products...">
309
+
310
+ <select (change)="onCategoryChange($event)">
311
+ <option value="">All Categories</option>
312
+ <option value="electronics">Electronics</option>
313
+ <option value="clothing">Clothing</option>
314
+ </select>
315
+ </div>
316
+
317
+ <div class="loading" *ngIf="state.isLoading$('list') | async">
318
+ Loading...
319
+ </div>
320
+
321
+ <div class="error" *ngIf="state.error$ | async as error">
322
+ {{ error }}
323
+ </div>
324
+
325
+ <table>
326
+ <thead>
327
+ <tr>
328
+ <th (click)="state.setSort('name')">Name</th>
329
+ <th (click)="state.setSort('price')">Price</th>
330
+ <th>Category</th>
331
+ <th>Actions</th>
332
+ </tr>
333
+ </thead>
334
+ <tbody>
335
+ <tr *ngFor="let product of state.records$ | async"
336
+ [class.selected]="state.isSelected(product)">
337
+ <td>{{ product.name }}</td>
338
+ <td>{{ product.price | currency }}</td>
339
+ <td>{{ product.category }}</td>
340
+ <td>
341
+ <button (click)="state.select(product)">Select</button>
342
+ <button (click)="editProduct(product)">Edit</button>
343
+ <button (click)="deleteProduct(product.id)">Delete</button>
344
+ </td>
345
+ </tr>
346
+ </tbody>
347
+ </table>
348
+
349
+ <div class="pagination" *ngIf="state.pager$ | async as pager">
350
+ <button
351
+ (click)="previousPage()"
352
+ [disabled]="!state.hasPreviousPage()">
353
+ Previous
354
+ </button>
355
+
356
+ <span>
357
+ Page {{ pager.currentPage }} of {{ pager.lastPage }}
358
+ ({{ pager.totalRecords }} total)
359
+ </span>
360
+
361
+ <button
362
+ (click)="nextPage()"
363
+ [disabled]="!state.hasMorePages()">
364
+ Next
365
+ </button>
366
+ </div>
367
+ `,
368
+ providers: [BaseStateService]
369
+ })
370
+ export class ProductsComponent implements OnInit, OnDestroy {
371
+ state = new BaseStateService<Product, ProductFilter>();
372
+
373
+ constructor(private productService: ProductService) {}
374
+
375
+ ngOnInit() {
376
+ // Subscribe to filter changes and load data
377
+ this.state.filter$.subscribe(filter => {
378
+ this.loadProducts(filter);
379
+ });
380
+
381
+ // Set initial filter
382
+ this.state.setFilter({
383
+ page: 1,
384
+ pageLength: 25,
385
+ category: 'electronics'
386
+ });
387
+ }
388
+
389
+ loadProducts(filter: ProductFilter) {
390
+ this.state.setLoading('list', true);
391
+ this.state.setError(null);
392
+
393
+ this.productService.getAll(filter).subscribe({
394
+ next: (response) => {
395
+ if (response.data) {
396
+ this.state.setApiResponse(response.data);
397
+ }
398
+ this.state.setLoading('list', false);
399
+ },
400
+ error: (err) => {
401
+ this.state.setError('Failed to load products');
402
+ this.state.setLoading('list', false);
403
+ }
404
+ });
405
+ }
406
+
407
+ onSearch(event: Event) {
408
+ const search = (event.target as HTMLInputElement).value;
409
+ this.state.setFilter({ search, page: 1 });
410
+ }
411
+
412
+ onCategoryChange(event: Event) {
413
+ const category = (event.target as HTMLSelectElement).value;
414
+ this.state.setFilter({ category, page: 1 });
415
+ }
416
+
417
+ nextPage() {
418
+ const currentPage = this.state.currentFilter.page || 1;
419
+ this.state.setPage(currentPage + 1);
420
+ }
421
+
422
+ previousPage() {
423
+ const currentPage = this.state.currentFilter.page || 1;
424
+ this.state.setPage(Math.max(1, currentPage - 1));
425
+ }
426
+
427
+ editProduct(product: Product) {
428
+ this.state.select(product);
429
+ // Open edit modal or navigate to edit page
430
+ }
431
+
432
+ deleteProduct(id: number) {
433
+ this.state.setLoading('delete', true);
434
+
435
+ this.productService.delete(id).subscribe({
436
+ next: () => {
437
+ this.state.removeRecordById(id);
438
+ this.state.setLoading('delete', false);
439
+ },
440
+ error: (err) => {
441
+ this.state.setError('Failed to delete product');
442
+ this.state.setLoading('delete', false);
443
+ }
444
+ });
445
+ }
446
+
447
+ ngOnDestroy() {
448
+ this.state.destroy();
449
+ }
450
+ }
451
+ ```
452
+
453
+ ---
454
+
455
+ ## 🔧 Utilities
456
+
457
+ ### Query Builder
458
+
459
+ Convert filter objects to URL query strings.
460
+
461
+ ```typescript
462
+ import { jsonToQueryString, isEmpty } from '@mysteryinfosolutions/api-core';
463
+
464
+ const filter = {
465
+ page: 1,
466
+ pageLength: 25,
467
+ search: 'laptop',
468
+ category: 'electronics',
469
+ tags: ['new', 'sale'],
470
+ sort: [
471
+ { field: 'price', order: 'ASC' },
472
+ { field: 'name', order: 'DESC' }
473
+ ]
474
+ };
475
+
476
+ const queryString = jsonToQueryString(filter);
477
+ // Result: "?page=1&pageLength=25&search=laptop&category=electronics&tags=[new,sale]&sort=price:ASC,name:DESC"
478
+
479
+ // Check if object is empty
480
+ isEmpty({}); // true
481
+ isEmpty({ page: 1 }); // false
482
+ ```
483
+
484
+ ### Permission Generator
485
+
486
+ Generate type-safe permission maps for resources.
487
+
488
+ ```typescript
489
+ import { generatePermissions } from '@mysteryinfosolutions/api-core';
490
+
491
+ // Standard permissions
492
+ const userPermissions = generatePermissions('user');
493
+ /* Result:
494
+ {
495
+ '*': 'user.*',
496
+ 'superAdmin': 'user.superAdmin',
497
+ 'view': 'user.view',
498
+ 'list': 'user.list',
499
+ 'create': 'user.create',
500
+ 'update': 'user.update',
501
+ 'delete': 'user.delete',
502
+ 'restore': 'user.restore',
503
+ 'forceDelete': 'user.forceDelete',
504
+ 'export': 'user.export',
505
+ 'import': 'user.import',
506
+ 'approve': 'user.approve',
507
+ 'reject': 'user.reject',
508
+ 'archive': 'user.archive',
509
+ 'unarchive': 'user.unarchive',
510
+ 'duplicate': 'user.duplicate',
511
+ 'share': 'user.share',
512
+ 'assign': 'user.assign',
513
+ 'changeStatus': 'user.changeStatus',
514
+ 'print': 'user.print',
515
+ 'preview': 'user.preview',
516
+ 'publish': 'user.publish',
517
+ 'unpublish': 'user.unpublish',
518
+ 'sync': 'user.sync',
519
+ 'audit': 'user.audit',
520
+ 'comment': 'user.comment',
521
+ 'favorite': 'user.favorite',
522
+ 'reorder': 'user.reorder',
523
+ 'toggleVisibility': 'user.toggleVisibility',
524
+ 'managePermissions': 'user.managePermissions',
525
+ 'assignRole': 'user.assignRole',
526
+ 'configure': 'user.configure',
527
+ // ... all 30+ standard permissions
528
+ }
529
+ */
530
+
531
+ // With custom permissions
532
+ const productPermissions = generatePermissions('product', ['discount', 'featured']);
533
+ /* Adds:
534
+ {
535
+ ...standardPermissions,
536
+ 'discount': 'product.discount',
537
+ 'featured': 'product.featured'
538
+ }
539
+ */
540
+
541
+ // Usage in component
542
+ @Component({
543
+ template: `
544
+ <button *ngIf="hasPermission(permissions.create)">
545
+ Create Product
546
+ </button>
547
+ `
548
+ })
549
+ export class ProductsComponent {
550
+ permissions = generatePermissions('product');
551
+
552
+ hasPermission(permission: string): boolean {
553
+ // Check with your auth service
554
+ return this.authService.hasPermission(permission);
555
+ }
556
+ }
57
557
  ```
58
558
 
59
- Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
559
+ ---
560
+
561
+ ## 🎨 Resource Configuration
562
+
563
+ Standardize table/grid configurations across your app.
564
+
565
+ ```typescript
566
+ import { BaseResourceConfig, generatePermissions } from '@mysteryinfosolutions/api-core';
567
+
568
+ interface Product {
569
+ id: number;
570
+ name: string;
571
+ price: number;
572
+ category: string;
573
+ createdAt: string;
574
+ updatedAt: string;
575
+ }
576
+
577
+ class ProductConfig extends BaseResourceConfig<Product> {
578
+ columns = [
579
+ {
580
+ key: 'id',
581
+ label: 'ID',
582
+ width: '80px',
583
+ isSortable: true
584
+ },
585
+ {
586
+ key: 'name',
587
+ label: 'Product Name',
588
+ isSortable: true,
589
+ type: 'text'
590
+ },
591
+ {
592
+ key: 'price',
593
+ label: 'Price',
594
+ isSortable: true,
595
+ pipe: 'currency',
596
+ width: '120px'
597
+ },
598
+ {
599
+ key: 'category',
600
+ label: 'Category',
601
+ isSortable: true
602
+ },
603
+ {
604
+ key: 'updatedAt',
605
+ label: 'Last Updated',
606
+ isSortable: true,
607
+ pipe: 'date',
608
+ hideOnMobile: true
609
+ }
610
+ ];
611
+
612
+ searchColumns = ['name', 'category'];
613
+ defaultSortColumn = 'updatedAt';
614
+ defaultSortOrder = 'DESC';
615
+ defaultPageLength = 25;
616
+ pageLengthOptions = [10, 25, 50, 100];
617
+
618
+ modifyModalSize = 'lg';
619
+ summaryModalSize = 'md';
620
+ defaultDetailView = 'summary';
621
+
622
+ permissions = generatePermissions('product');
623
+ }
624
+
625
+ // Usage
626
+ const config = new ProductConfig();
627
+ ```
628
+
629
+ ---
630
+
631
+ ## 📋 Models & Types
632
+
633
+ ### IResponse<T>
634
+
635
+ Standard API response wrapper.
636
+
637
+ ```typescript
638
+ interface IResponse<T> {
639
+ status?: number;
640
+ data?: T | null;
641
+ error?: IMisError;
642
+ infoDtls?: any;
643
+ }
644
+ ```
645
+
646
+ ### IMultiresult<T>
647
+
648
+ Paginated list response.
649
+
650
+ ```typescript
651
+ interface IMultiresult<T> {
652
+ records: T[];
653
+ pager: IMultiresultMetaData;
654
+ }
655
+
656
+ interface IMultiresultMetaData {
657
+ totalRecords: number;
658
+ previous?: number;
659
+ currentPage?: number;
660
+ next?: number;
661
+ perPage?: number;
662
+ segment?: number;
663
+ lastPage?: number;
664
+ }
665
+ ```
666
+
667
+ ### Filter
668
+
669
+ Base filter class with pagination and sorting.
670
+
671
+ ```typescript
672
+ abstract class Filter {
673
+ ids?: number[];
674
+ dateRangeColumn?: string;
675
+ dateRangeFrom?: string;
676
+ dateRangeTo?: string;
677
+
678
+ page?: number;
679
+ pageLength?: number;
680
+
681
+ sort?: SortItem[];
682
+
683
+ search?: string;
684
+ searchColumns?: string;
685
+ selectColumns?: string;
686
+ selectMode?: SELECT_MODE;
687
+ }
688
+ ```
689
+
690
+ ### SortItem
691
+
692
+ Sort configuration.
693
+
694
+ ```typescript
695
+ class SortItem {
696
+ constructor(
697
+ public field: string,
698
+ public order: 'ASC' | 'DESC'
699
+ ) {}
700
+ }
701
+ ```
702
+
703
+ ### TableColumn<T>
704
+
705
+ Column configuration for tables.
706
+
707
+ ```typescript
708
+ interface TableColumn<T = any> {
709
+ key: keyof T | string;
710
+ label: string;
711
+ isSortable?: boolean;
712
+ width?: string;
713
+ type?: 'text' | 'checkbox' | 'action' | 'custom';
714
+ valueGetter?: (row: T) => any;
715
+ hideOnMobile?: boolean;
716
+ pipe?: 'date' | 'currency' | 'uppercase' | 'lowercase' | string;
717
+ cellClass?: string;
718
+ headerClass?: string;
719
+ visible?: boolean;
720
+ }
721
+ ```
722
+
723
+ ---
724
+
725
+ ## 🔌 API Format Requirements
726
+
727
+ This library expects your backend API to follow this format:
728
+
729
+ ### Response Format
730
+
731
+ **Success Response:**
732
+ ```json
733
+ {
734
+ "status": 200,
735
+ "data": { /* your data */ },
736
+ "infoDtls": null
737
+ }
738
+ ```
739
+
740
+ **List Response:**
741
+ ```json
742
+ {
743
+ "status": 200,
744
+ "data": {
745
+ "records": [
746
+ { "id": 1, "name": "Item 1" },
747
+ { "id": 2, "name": "Item 2" }
748
+ ],
749
+ "pager": {
750
+ "totalRecords": 100,
751
+ "currentPage": 1,
752
+ "lastPage": 10,
753
+ "perPage": 10,
754
+ "next": 2,
755
+ "previous": null
756
+ }
757
+ }
758
+ }
759
+ ```
760
+
761
+ **Error Response:**
762
+ ```json
763
+ {
764
+ "status": 400,
765
+ "data": null,
766
+ "error": {
767
+ "code": "VALIDATION_ERROR",
768
+ "message": "Invalid input",
769
+ "details": "Email is required"
770
+ }
771
+ }
772
+ ```
773
+
774
+ ### Query String Format
775
+
776
+ **Pagination:**
777
+ ```
778
+ ?page=1&pageLength=25
779
+ ```
780
+
781
+ **Sorting:**
782
+ ```
783
+ ?sort=name:ASC,createdAt:DESC
784
+ ```
785
+
786
+ **Filtering:**
787
+ ```
788
+ ?search=laptop&category=electronics&minPrice=100
789
+ ```
790
+
791
+ **Combined:**
792
+ ```
793
+ ?page=1&pageLength=25&sort=price:ASC&search=laptop&category=electronics
794
+ ```
795
+
796
+ ---
797
+
798
+ ## 🏗 Best Practices
799
+
800
+ ### 1. Service Pattern
801
+
802
+ ```typescript
803
+ // ✅ Good: Extend BaseService
804
+ @Injectable({ providedIn: 'root' })
805
+ export class UserService extends BaseService<User, UserFilter> {
806
+ constructor(http: HttpClient) {
807
+ super(http, '/api/users');
808
+ }
809
+
810
+ // Add custom methods
811
+ activateUser(id: number): Observable<IResponse<User>> {
812
+ return this.http.post<IResponse<User>>(`${this.baseUrl}/${id}/activate`, {});
813
+ }
814
+ }
815
+
816
+ // ❌ Bad: Don't reimplement CRUD
817
+ @Injectable({ providedIn: 'root' })
818
+ export class UserService {
819
+ getAll() { /* duplicate code */ }
820
+ getDetails() { /* duplicate code */ }
821
+ // ...
822
+ }
823
+ ```
824
+
825
+ ### 2. State Management
826
+
827
+ ```typescript
828
+ // ✅ Good: Use BaseStateService for complex lists
829
+ @Component({
830
+ providers: [BaseStateService] // Component-level
831
+ })
832
+ export class UsersComponent {
833
+ state = new BaseStateService<User, UserFilter>();
834
+
835
+ ngOnDestroy() {
836
+ this.state.destroy(); // Clean up
837
+ }
838
+ }
839
+
840
+ // ✅ Good: Direct observable for simple cases
841
+ @Component({})
842
+ export class UserDetailComponent {
843
+ user$ = this.userService.getDetails(this.userId);
844
+ }
845
+ ```
846
+
847
+ ### 3. Error Handling
848
+
849
+ ```typescript
850
+ // ✅ Good: Handle errors properly
851
+ loadUsers() {
852
+ this.state.setLoading('list', true);
853
+ this.state.setError(null);
854
+
855
+ this.userService.getAll(this.state.currentFilter).subscribe({
856
+ next: (response) => {
857
+ if (response.data) {
858
+ this.state.setApiResponse(response.data);
859
+ } else if (response.error) {
860
+ this.state.setError(response.error.message || 'Failed to load');
861
+ }
862
+ this.state.setLoading('list', false);
863
+ },
864
+ error: (err) => {
865
+ this.state.setError('Network error occurred');
866
+ this.state.setLoading('list', false);
867
+ console.error('Error loading users:', err);
868
+ }
869
+ });
870
+ }
871
+ ```
872
+
873
+ ### 4. Filter Management
874
+
875
+ ```typescript
876
+ // ✅ Good: Reset page when filter changes
877
+ onCategoryChange(category: string) {
878
+ this.state.setFilter({
879
+ category,
880
+ page: 1 // Reset to first page
881
+ });
882
+ }
883
+
884
+ // ✅ Good: Debounce search input
885
+ searchInput$ = new Subject<string>();
886
+
887
+ ngOnInit() {
888
+ this.searchInput$.pipe(
889
+ debounceTime(300),
890
+ distinctUntilChanged()
891
+ ).subscribe(search => {
892
+ this.state.setFilter({ search, page: 1 });
893
+ });
894
+ }
895
+ ```
896
+
897
+ ### 5. Permissions
898
+
899
+ ```typescript
900
+ // ✅ Good: Generate once, reuse everywhere
901
+ export class UserConfig extends BaseResourceConfig<User> {
902
+ permissions = generatePermissions('user', ['resetPassword', 'sendInvite']);
903
+ }
904
+
905
+ // In component
906
+ if (this.authService.hasPermission(config.permissions.create)) {
907
+ // Show create button
908
+ }
909
+ ```
910
+
911
+ ---
912
+
913
+ ## 🔄 Migration from v1.x to v2.x
914
+
915
+ When updating to future versions, check the CHANGELOG.md for breaking changes.
916
+
917
+ ---
918
+
919
+ ## 🤝 Contributing
920
+
921
+ Contributions are welcome! Please follow these guidelines:
922
+
923
+ 1. Fork the repository
924
+ 2. Create a feature branch
925
+ 3. Write tests for new features
926
+ 4. Ensure all tests pass
927
+ 5. Submit a pull request
928
+
929
+ ---
930
+
931
+ ## 📄 License
932
+
933
+ MIT License - see LICENSE file for details
934
+
935
+ ---
936
+
937
+ ## 🐛 Issues & Support
938
+
939
+ For issues, questions, or feature requests:
940
+ - GitHub Issues: [Create an issue](https://github.com/mysteryinfosolutions/angular-libraries/issues)
941
+ - Email: support@mysteryinfosolutions.com
942
+
943
+ ---
944
+
945
+ ## 🙏 Credits
946
+
947
+ Developed and maintained by **Mystery Info Solutions**.
948
+
949
+ ---
950
+
951
+ ## 📚 Additional Resources
952
+
953
+ - [Angular Documentation](https://angular.dev)
954
+ - [RxJS Documentation](https://rxjs.dev)
955
+ - [TypeScript Documentation](https://www.typescriptlang.org)
60
956
 
61
- ## Additional Resources
957
+ ---
62
958
 
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.
959
+ **Version:** 1.8.0
960
+ **Last Updated:** November 2024