@flusys/ng-shared 3.0.0-rc → 3.0.1
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 +603 -322
- package/fesm2022/flusys-ng-shared.mjs +193 -283
- package/fesm2022/flusys-ng-shared.mjs.map +1 -1
- package/package.json +9 -9
- package/types/flusys-ng-shared.d.ts +63 -169
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
## Package Information
|
|
10
10
|
|
|
11
11
|
- **Package:** `@flusys/ng-shared`
|
|
12
|
+
- **Version:** 3.0.1
|
|
12
13
|
- **Dependencies:** ng-core
|
|
13
14
|
- **Dependents:** ng-layout, ng-auth, ng-iam, ng-storage, flusysng
|
|
14
15
|
- **Build Command:** `npm run build:ng-shared`
|
|
@@ -37,13 +38,13 @@ interface IBaseEntity {
|
|
|
37
38
|
|
|
38
39
|
**Entity Mixins:**
|
|
39
40
|
|
|
40
|
-
| Interface
|
|
41
|
-
|
|
|
42
|
-
| `ISoftDeletable
|
|
43
|
-
| `ITimestampable
|
|
44
|
-
| `IActivatable`
|
|
45
|
-
| `IOrderable`
|
|
46
|
-
| `IMetadata`
|
|
41
|
+
| Interface | Fields | Purpose |
|
|
42
|
+
| ---------------- | ------------------------------------ | -------------------- |
|
|
43
|
+
| `ISoftDeletable` | `deletedAt?: Date \| null` | Soft delete support |
|
|
44
|
+
| `ITimestampable` | `createdAt, updatedAt` | Timestamp tracking |
|
|
45
|
+
| `IActivatable` | `isActive: boolean` | Active status toggle |
|
|
46
|
+
| `IOrderable` | `serial?: number \| null` | Ordering/sorting |
|
|
47
|
+
| `IMetadata` | `metadata?: Record<string, unknown>` | JSON metadata field |
|
|
47
48
|
|
|
48
49
|
#### ILoggedUserInfo
|
|
49
50
|
|
|
@@ -73,8 +74,14 @@ interface IPagination {
|
|
|
73
74
|
currentPage: number;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
interface ISort {
|
|
77
|
-
|
|
77
|
+
interface ISort {
|
|
78
|
+
[key: string]: "ASC" | "DESC";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Filter supports primitives and arrays for multi-value filtering
|
|
82
|
+
interface IFilter {
|
|
83
|
+
[key: string]: string | number | boolean | null | undefined | string[] | number[];
|
|
84
|
+
}
|
|
78
85
|
|
|
79
86
|
interface IFilterData {
|
|
80
87
|
filter?: IFilter;
|
|
@@ -86,15 +93,17 @@ interface IFilterData {
|
|
|
86
93
|
}
|
|
87
94
|
```
|
|
88
95
|
|
|
96
|
+
**Note:** `IFilter` supports array values (`string[]`, `number[]`) for multi-value filtering (e.g., filtering by multiple status values or IDs).
|
|
97
|
+
|
|
89
98
|
#### IDeleteData
|
|
90
99
|
|
|
91
100
|
Delete request payload matching backend DeleteDto.
|
|
92
101
|
|
|
93
102
|
```typescript
|
|
94
|
-
type DeleteType =
|
|
103
|
+
type DeleteType = "delete" | "restore" | "permanent";
|
|
95
104
|
|
|
96
105
|
interface IDeleteData {
|
|
97
|
-
id: string | string[];
|
|
106
|
+
id: string | string[]; // Single or batch delete
|
|
98
107
|
type: DeleteType;
|
|
99
108
|
}
|
|
100
109
|
```
|
|
@@ -117,7 +126,6 @@ interface IUserSelectFilter {
|
|
|
117
126
|
page: number;
|
|
118
127
|
pageSize: number;
|
|
119
128
|
search: string;
|
|
120
|
-
[key: string]: unknown;
|
|
121
129
|
}
|
|
122
130
|
|
|
123
131
|
type LoadUsersFn = (filter: IUserSelectFilter) => Observable<IListResponse<IUserBasicInfo>>;
|
|
@@ -144,6 +152,7 @@ interface IFileUploadOptions {
|
|
|
144
152
|
}
|
|
145
153
|
|
|
146
154
|
interface IUploadedFile {
|
|
155
|
+
id?: string; // File manager ID (UUID) - available when registered
|
|
147
156
|
name: string;
|
|
148
157
|
key: string;
|
|
149
158
|
size: number;
|
|
@@ -156,7 +165,6 @@ interface IFileSelectFilter {
|
|
|
156
165
|
search: string;
|
|
157
166
|
contentTypes?: string[];
|
|
158
167
|
folderId?: string;
|
|
159
|
-
[key: string]: unknown;
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
type LoadFilesFn = (filter: IFileSelectFilter) => Observable<IListResponse<IFileBasicInfo>>;
|
|
@@ -167,18 +175,18 @@ type GetFileUrlsFn = (fileIds: string[]) => Observable<ISingleResponse<IFileBasi
|
|
|
167
175
|
**File Type Filters (Constants):**
|
|
168
176
|
|
|
169
177
|
```typescript
|
|
170
|
-
import { FILE_TYPE_FILTERS, getAcceptString, isFileTypeAllowed, getFileIconClass, formatFileSize } from
|
|
171
|
-
|
|
172
|
-
FILE_TYPE_FILTERS.IMAGES
|
|
173
|
-
FILE_TYPE_FILTERS.DOCUMENTS
|
|
174
|
-
FILE_TYPE_FILTERS.VIDEOS
|
|
175
|
-
FILE_TYPE_FILTERS.AUDIO
|
|
176
|
-
FILE_TYPE_FILTERS.ALL
|
|
177
|
-
|
|
178
|
-
getAcceptString([
|
|
179
|
-
isFileTypeAllowed(file, [
|
|
180
|
-
getFileIconClass(
|
|
181
|
-
formatFileSize(1024)
|
|
178
|
+
import { FILE_TYPE_FILTERS, getAcceptString, isFileTypeAllowed, getFileIconClass, formatFileSize } from "@flusys/ng-shared";
|
|
179
|
+
|
|
180
|
+
FILE_TYPE_FILTERS.IMAGES; // ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml']
|
|
181
|
+
FILE_TYPE_FILTERS.DOCUMENTS; // ['application/pdf', 'application/msword', ...]
|
|
182
|
+
FILE_TYPE_FILTERS.VIDEOS; // ['video/mp4', 'video/webm', ...]
|
|
183
|
+
FILE_TYPE_FILTERS.AUDIO; // ['audio/mpeg', 'audio/wav', ...]
|
|
184
|
+
FILE_TYPE_FILTERS.ALL; // [] (allows all)
|
|
185
|
+
|
|
186
|
+
getAcceptString(["image/*"]); // 'image/*'
|
|
187
|
+
isFileTypeAllowed(file, ["image/*"]); // true/false
|
|
188
|
+
getFileIconClass("image/png"); // 'pi pi-image'
|
|
189
|
+
formatFileSize(1024); // '1 KB'
|
|
182
190
|
```
|
|
183
191
|
|
|
184
192
|
### Response Interfaces
|
|
@@ -234,12 +242,12 @@ type ApiResponse<T> = ISingleResponse<T> | IListResponse<T> | IBulkResponse<T> |
|
|
|
234
242
|
|
|
235
243
|
**Metadata types:**
|
|
236
244
|
|
|
237
|
-
| Interface | Fields
|
|
238
|
-
| ------------------ |
|
|
239
|
-
| `IRequestMeta` | `requestId?, timestamp?, responseTime?`
|
|
245
|
+
| Interface | Fields |
|
|
246
|
+
| ------------------ | ----------------------------------------------------- |
|
|
247
|
+
| `IRequestMeta` | `requestId?, timestamp?, responseTime?` |
|
|
240
248
|
| `IPaginationMeta` | `total, page, pageSize, count, hasMore?, totalPages?` |
|
|
241
|
-
| `IBulkMeta` | `count, failed?, total?`
|
|
242
|
-
| `IValidationError` | `field, message, constraint?`
|
|
249
|
+
| `IBulkMeta` | `count, failed?, total?` |
|
|
250
|
+
| `IValidationError` | `field, message, constraint?` |
|
|
243
251
|
|
|
244
252
|
### Auth & Storage Response Types
|
|
245
253
|
|
|
@@ -267,6 +275,14 @@ interface IFileData {
|
|
|
267
275
|
thumbnailUrl?: string;
|
|
268
276
|
createdAt: Date;
|
|
269
277
|
}
|
|
278
|
+
|
|
279
|
+
// File URL service response DTO
|
|
280
|
+
interface FilesResponseDto {
|
|
281
|
+
id: string;
|
|
282
|
+
name: string;
|
|
283
|
+
contentType: string;
|
|
284
|
+
url: string | null;
|
|
285
|
+
}
|
|
270
286
|
```
|
|
271
287
|
|
|
272
288
|
### Permission Interfaces
|
|
@@ -276,14 +292,14 @@ Discriminated union for building complex permission logic trees.
|
|
|
276
292
|
```typescript
|
|
277
293
|
// Single permission check
|
|
278
294
|
interface IActionNode {
|
|
279
|
-
type:
|
|
295
|
+
type: "action";
|
|
280
296
|
actionId: string;
|
|
281
297
|
}
|
|
282
298
|
|
|
283
299
|
// Group with AND/OR logic
|
|
284
300
|
interface IGroupNode {
|
|
285
|
-
type:
|
|
286
|
-
operator:
|
|
301
|
+
type: "group";
|
|
302
|
+
operator: "AND" | "OR";
|
|
287
303
|
children: ILogicNode[];
|
|
288
304
|
}
|
|
289
305
|
|
|
@@ -310,58 +326,149 @@ enum IconTypeEnum {
|
|
|
310
326
|
|
|
311
327
|
---
|
|
312
328
|
|
|
313
|
-
## 3.
|
|
329
|
+
## 3. Constants
|
|
330
|
+
|
|
331
|
+
### Permission Constants
|
|
332
|
+
|
|
333
|
+
Centralized permission codes for type-safe permission checks. Single source of truth to prevent typos.
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { PERMISSIONS, USER_PERMISSIONS, ROLE_PERMISSIONS } from '@flusys/ng-shared';
|
|
337
|
+
|
|
338
|
+
// Use constants instead of strings
|
|
339
|
+
*hasPermission="PERMISSIONS.USER.READ"
|
|
340
|
+
|
|
341
|
+
// Individual permission groups available:
|
|
342
|
+
USER_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
|
|
343
|
+
COMPANY_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
|
|
344
|
+
BRANCH_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
|
|
345
|
+
ACTION_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
|
|
346
|
+
ROLE_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
|
|
347
|
+
ROLE_ACTION_PERMISSIONS // { READ, ASSIGN }
|
|
348
|
+
USER_ROLE_PERMISSIONS // { READ, ASSIGN }
|
|
349
|
+
USER_ACTION_PERMISSIONS // { READ, ASSIGN }
|
|
350
|
+
COMPANY_ACTION_PERMISSIONS // { READ, ASSIGN }
|
|
351
|
+
FILE_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
|
|
352
|
+
FOLDER_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
|
|
353
|
+
STORAGE_CONFIG_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
|
|
354
|
+
EMAIL_CONFIG_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
|
|
355
|
+
EMAIL_TEMPLATE_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
|
|
356
|
+
FORM_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
|
|
357
|
+
|
|
358
|
+
// Aggregated object with all permissions
|
|
359
|
+
PERMISSIONS.USER.READ // 'user.read'
|
|
360
|
+
PERMISSIONS.ROLE.CREATE // 'role.create'
|
|
361
|
+
PERMISSIONS.FILE.DELETE // 'file.delete'
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Type:** `PermissionCode` - Union type of all valid permission code strings.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## 4. Services
|
|
314
369
|
|
|
315
370
|
### ApiResourceService
|
|
316
371
|
|
|
317
|
-
Signal-based CRUD service using Angular 21 `resource()` API
|
|
372
|
+
Signal-based CRUD service using Angular 21 `resource()` API with **lazy initialization**. All endpoints use POST (RPC-style).
|
|
373
|
+
|
|
374
|
+
**ServiceName Type:**
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
type ServiceName = "auth" | "administration" | "iam" | "storage" | "formBuilder" | "email";
|
|
378
|
+
```
|
|
318
379
|
|
|
319
380
|
**Define a service:**
|
|
320
381
|
|
|
321
382
|
```typescript
|
|
322
|
-
import { Injectable } from
|
|
323
|
-
import { HttpClient } from
|
|
324
|
-
import { ApiResourceService } from
|
|
383
|
+
import { Injectable } from "@angular/core";
|
|
384
|
+
import { HttpClient } from "@angular/common/http";
|
|
385
|
+
import { ApiResourceService } from "@flusys/ng-shared";
|
|
325
386
|
|
|
326
|
-
@Injectable({ providedIn:
|
|
387
|
+
@Injectable({ providedIn: "root" })
|
|
327
388
|
export class UserService extends ApiResourceService<UserDto, IUser> {
|
|
328
389
|
constructor(http: HttpClient) {
|
|
329
|
-
|
|
390
|
+
// Option 1: Use global apiBaseUrl (default)
|
|
391
|
+
super("users", http);
|
|
392
|
+
// Base URL: APP_CONFIG.apiBaseUrl + '/users'
|
|
393
|
+
|
|
394
|
+
// Option 2: Use feature-specific service URL (recommended)
|
|
395
|
+
super("users", http, "administration");
|
|
396
|
+
// Base URL: APP_CONFIG.services.administration.baseUrl + '/users'
|
|
330
397
|
}
|
|
331
398
|
}
|
|
332
399
|
```
|
|
333
400
|
|
|
401
|
+
**Constructor Parameters:**
|
|
402
|
+
|
|
403
|
+
| Parameter | Type | Required | Description |
|
|
404
|
+
| --------------- | ------------- | -------- | ------------------------------------------------ |
|
|
405
|
+
| `moduleApiName` | `string` | Yes | API path segment (e.g., 'users', 'file-manager') |
|
|
406
|
+
| `http` | `HttpClient` | Yes | Angular HttpClient instance |
|
|
407
|
+
| `serviceName` | `ServiceName` | No | Feature service name for URL resolution |
|
|
408
|
+
|
|
409
|
+
**Service URL Resolution:**
|
|
410
|
+
|
|
411
|
+
- If `serviceName` is provided, uses `getServiceUrl(config, serviceName)` to resolve the base URL
|
|
412
|
+
- Falls back to `APP_CONFIG.apiBaseUrl` if service not found or `serviceName` not provided
|
|
413
|
+
|
|
414
|
+
**Lazy Initialization:**
|
|
415
|
+
|
|
416
|
+
The list resource is **lazy-initialized** to avoid unnecessary HTTP requests on service construction. The resource is only created when first needed:
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
// Resource is NOT created until one of these is called:
|
|
420
|
+
userService.fetchList("", { pagination: { currentPage: 0, pageSize: 10 } });
|
|
421
|
+
// OR
|
|
422
|
+
userService.initListResource(); // Explicit initialization
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**Internal resource structure:**
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
private _listResource: ResourceRef<IListResponse<InterfaceT> | undefined> | null = null;
|
|
429
|
+
private _resourceInitialized = false;
|
|
430
|
+
private readonly _resourceInitSignal = signal(false);
|
|
431
|
+
|
|
432
|
+
// Initialize the list resource (called automatically by fetchList)
|
|
433
|
+
initListResource(): void {
|
|
434
|
+
if (this._resourceInitialized) return;
|
|
435
|
+
this._resourceInitialized = true;
|
|
436
|
+
this._resourceInitSignal.set(true);
|
|
437
|
+
// Creates the resource with linkedSignal for data, isLoading, etc.
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
334
441
|
**Endpoint mapping:**
|
|
335
442
|
|
|
336
|
-
| Method
|
|
337
|
-
|
|
|
338
|
-
| `insert(dto)`
|
|
339
|
-
| `insertMany(dtos)`
|
|
340
|
-
| `findById(id, select?)`
|
|
341
|
-
| `getAll(search, filter)` | POST | `/{resource}/get-all?q=`
|
|
342
|
-
| `update(dto)`
|
|
343
|
-
| `updateMany(dtos)`
|
|
344
|
-
| `delete(deleteDto)`
|
|
443
|
+
| Method | HTTP | Endpoint | Response |
|
|
444
|
+
| ------------------------ | ---- | ------------------------- | -------------------- |
|
|
445
|
+
| `insert(dto)` | POST | `/{resource}/insert` | `ISingleResponse<T>` |
|
|
446
|
+
| `insertMany(dtos)` | POST | `/{resource}/insert-many` | `IBulkResponse<T>` |
|
|
447
|
+
| `findById(id, select?)` | POST | `/{resource}/get/:id` | `ISingleResponse<T>` |
|
|
448
|
+
| `getAll(search, filter)` | POST | `/{resource}/get-all?q=` | `IListResponse<T>` |
|
|
449
|
+
| `update(dto)` | POST | `/{resource}/update` | `ISingleResponse<T>` |
|
|
450
|
+
| `updateMany(dtos)` | POST | `/{resource}/update-many` | `IBulkResponse<T>` |
|
|
451
|
+
| `delete(deleteDto)` | POST | `/{resource}/delete` | `IMessageResponse` |
|
|
345
452
|
|
|
346
453
|
All methods above return `Observable`. Async (Promise) variants are also available: `insertAsync`, `insertManyAsync`, `findByIdAsync`, `updateAsync`, `updateManyAsync`, `deleteAsync`.
|
|
347
454
|
|
|
348
455
|
**Reactive signals:**
|
|
349
456
|
|
|
350
|
-
| Signal | Type
|
|
351
|
-
| ------------ |
|
|
352
|
-
| `isLoading` | `Signal<boolean>`
|
|
353
|
-
| `data` | `Signal<T[]>`
|
|
354
|
-
| `total` | `Signal<number>`
|
|
355
|
-
| `pageInfo` | `Signal<IPaginationMeta>`
|
|
356
|
-
| `hasMore` | `Signal<boolean>`
|
|
357
|
-
| `searchTerm` | `WritableSignal<string>`
|
|
457
|
+
| Signal | Type | Description |
|
|
458
|
+
| ------------ | ----------------------------- | ----------------------- |
|
|
459
|
+
| `isLoading` | `Signal<boolean>` | Whether data is loading |
|
|
460
|
+
| `data` | `Signal<T[]>` | Current list data |
|
|
461
|
+
| `total` | `Signal<number>` | Total item count |
|
|
462
|
+
| `pageInfo` | `Signal<IPaginationMeta>` | Pagination metadata |
|
|
463
|
+
| `hasMore` | `Signal<boolean>` | More pages available |
|
|
464
|
+
| `searchTerm` | `WritableSignal<string>` | Current search term |
|
|
358
465
|
| `filterData` | `WritableSignal<IFilterData>` | Filter/pagination state |
|
|
359
466
|
|
|
360
467
|
**List management:**
|
|
361
468
|
|
|
362
469
|
```typescript
|
|
363
470
|
// Trigger data fetch (updates searchTerm and filterData signals, resource auto-reloads)
|
|
364
|
-
userService.fetchList(
|
|
471
|
+
userService.fetchList("search term", { pagination: { currentPage: 0, pageSize: 10 } });
|
|
365
472
|
|
|
366
473
|
// Pagination helpers
|
|
367
474
|
userService.setPagination({ currentPage: 1, pageSize: 20 });
|
|
@@ -424,18 +531,18 @@ export class ProductComponent {
|
|
|
424
531
|
|
|
425
532
|
**Methods:**
|
|
426
533
|
|
|
427
|
-
| Method
|
|
428
|
-
|
|
|
429
|
-
| `getFileUrl(fileId)`
|
|
430
|
-
| `fileUrlSignal(fileId)`
|
|
431
|
-
| `fetchFileUrls(fileIds[])`
|
|
432
|
-
| `fetchSingleFileUrl(fileId)`
|
|
433
|
-
| `clearCache()`
|
|
434
|
-
| `removeFromCache(fileId)`
|
|
534
|
+
| Method | Returns | Description |
|
|
535
|
+
| ---------------------------- | -------------------------------------- | --------------------------------- |
|
|
536
|
+
| `getFileUrl(fileId)` | `string \| null` | Get cached URL (synchronous) |
|
|
537
|
+
| `fileUrlSignal(fileId)` | `Signal<string \| null>` | Computed signal from cache |
|
|
538
|
+
| `fetchFileUrls(fileIds[])` | `Observable<FilesResponseDto[]>` | Fetch from backend, updates cache |
|
|
539
|
+
| `fetchSingleFileUrl(fileId)` | `Observable<FilesResponseDto \| null>` | Fetch single file |
|
|
540
|
+
| `clearCache()` | `void` | Clear all cached URLs |
|
|
541
|
+
| `removeFromCache(fileId)` | `void` | Remove specific entry from cache |
|
|
435
542
|
|
|
436
543
|
### PermissionValidatorService
|
|
437
544
|
|
|
438
|
-
Signal-based permission state management. Used by `HasPermissionDirective`, permission guards, and IAM.
|
|
545
|
+
Signal-based permission state management. Used by `HasPermissionDirective`, permission guards, and IAM. Supports **wildcard permissions**.
|
|
439
546
|
|
|
440
547
|
```typescript
|
|
441
548
|
import { PermissionValidatorService } from '@flusys/ng-shared';
|
|
@@ -453,8 +560,8 @@ export class MyComponent {
|
|
|
453
560
|
// User has permission
|
|
454
561
|
}
|
|
455
562
|
|
|
456
|
-
// Check loaded state
|
|
457
|
-
if (this.permissionValidator.
|
|
563
|
+
// Check loaded state (signal-based)
|
|
564
|
+
if (this.permissionValidator.isLoaded()) {
|
|
458
565
|
// Permissions have been loaded
|
|
459
566
|
}
|
|
460
567
|
}
|
|
@@ -463,25 +570,41 @@ export class MyComponent {
|
|
|
463
570
|
|
|
464
571
|
**Methods:**
|
|
465
572
|
|
|
466
|
-
| Method
|
|
467
|
-
|
|
|
468
|
-
| `setPermissions(codes[])`
|
|
469
|
-
| `clearPermissions()`
|
|
470
|
-
| `hasPermission(code)`
|
|
471
|
-
| `isPermissionsLoaded()`
|
|
573
|
+
| Method | Returns | Description |
|
|
574
|
+
| ------------------------- | --------- | -------------------------------------------- |
|
|
575
|
+
| `setPermissions(codes[])` | `void` | Replace all permissions |
|
|
576
|
+
| `clearPermissions()` | `void` | Clear all permissions |
|
|
577
|
+
| `hasPermission(code)` | `boolean` | Check single permission (supports wildcards) |
|
|
578
|
+
| `isPermissionsLoaded()` | `boolean` | **Deprecated** - Use `isLoaded()` signal |
|
|
472
579
|
|
|
473
580
|
**Signals:**
|
|
474
581
|
|
|
475
|
-
| Signal | Type | Description
|
|
476
|
-
| ------------- | ------------------ |
|
|
477
|
-
| `permissions` | `Signal<string[]>` | Readonly current permissions
|
|
582
|
+
| Signal | Type | Description |
|
|
583
|
+
| ------------- | ------------------ | --------------------------------- |
|
|
584
|
+
| `permissions` | `Signal<string[]>` | Readonly current permissions |
|
|
585
|
+
| `isLoaded` | `Signal<boolean>` | Whether permissions have been set |
|
|
586
|
+
|
|
587
|
+
**Wildcard Support:**
|
|
588
|
+
|
|
589
|
+
The `hasPermission()` method uses the permission evaluator utility which supports wildcards:
|
|
590
|
+
|
|
591
|
+
```typescript
|
|
592
|
+
// Exact match
|
|
593
|
+
hasPermission("user.read"); // true if permissions include 'user.read'
|
|
594
|
+
|
|
595
|
+
// Global wildcard
|
|
596
|
+
hasPermission("user.read"); // true if permissions include '*'
|
|
597
|
+
|
|
598
|
+
// Module wildcard
|
|
599
|
+
hasPermission("user.read"); // true if permissions include 'user.*'
|
|
600
|
+
```
|
|
478
601
|
|
|
479
602
|
### CookieService
|
|
480
603
|
|
|
481
604
|
SSR-aware cookie reading service.
|
|
482
605
|
|
|
483
606
|
```typescript
|
|
484
|
-
import { CookieService } from
|
|
607
|
+
import { CookieService } from "@flusys/ng-shared";
|
|
485
608
|
|
|
486
609
|
const cookies = inject(CookieService).get(); // Returns document.cookie (browser) or request header cookie (server)
|
|
487
610
|
```
|
|
@@ -491,7 +614,7 @@ const cookies = inject(CookieService).get(); // Returns document.cookie (browser
|
|
|
491
614
|
SSR environment detection service.
|
|
492
615
|
|
|
493
616
|
```typescript
|
|
494
|
-
import { PlatformService } from
|
|
617
|
+
import { PlatformService } from "@flusys/ng-shared";
|
|
495
618
|
|
|
496
619
|
const platform = inject(PlatformService);
|
|
497
620
|
if (!platform.isServer) {
|
|
@@ -501,7 +624,7 @@ if (!platform.isServer) {
|
|
|
501
624
|
|
|
502
625
|
---
|
|
503
626
|
|
|
504
|
-
##
|
|
627
|
+
## 5. Components
|
|
505
628
|
|
|
506
629
|
### IconComponent
|
|
507
630
|
|
|
@@ -532,21 +655,7 @@ Single-select dropdown with lazy loading, search, and scroll pagination.
|
|
|
532
655
|
```typescript
|
|
533
656
|
@Component({
|
|
534
657
|
imports: [LazySelectComponent],
|
|
535
|
-
template: `
|
|
536
|
-
<lib-lazy-select
|
|
537
|
-
[(value)]="selectedId"
|
|
538
|
-
[selectDataList]="items()"
|
|
539
|
-
[optionLabel]="'label'"
|
|
540
|
-
[optionValue]="'value'"
|
|
541
|
-
[isEditMode]="true"
|
|
542
|
-
[isLoading]="loading()"
|
|
543
|
-
[total]="total()"
|
|
544
|
-
[pagination]="pagination()"
|
|
545
|
-
[placeHolder]="'Select item'"
|
|
546
|
-
(onSearch)="handleSearch($event)"
|
|
547
|
-
(onPagination)="handlePagination($event)"
|
|
548
|
-
/>
|
|
549
|
-
`,
|
|
658
|
+
template: ` <lib-lazy-select [(value)]="selectedId" [selectDataList]="items()" [optionLabel]="'label'" [optionValue]="'value'" [isEditMode]="true" [isLoading]="loading()" [total]="total()" [pagination]="pagination()" [placeHolder]="'Select item'" (onSearch)="handleSearch($event)" (onPagination)="handlePagination($event)" /> `,
|
|
550
659
|
})
|
|
551
660
|
export class MyComponent {
|
|
552
661
|
readonly selectedId = signal<string | null>(null);
|
|
@@ -559,16 +668,16 @@ export class MyComponent {
|
|
|
559
668
|
|
|
560
669
|
**Inputs:**
|
|
561
670
|
|
|
562
|
-
| Input | Type
|
|
563
|
-
| ---------------- |
|
|
564
|
-
| `selectDataList` | `Array<IDropDown>`
|
|
565
|
-
| `optionLabel` | `string`
|
|
566
|
-
| `optionValue` | `string`
|
|
567
|
-
| `isEditMode` | `boolean`
|
|
568
|
-
| `isLoading` | `boolean`
|
|
569
|
-
| `total` | `number \| undefined` | Total items for pagination
|
|
570
|
-
| `pagination` | `IPagination`
|
|
571
|
-
| `placeHolder` | `string`
|
|
671
|
+
| Input | Type | Description |
|
|
672
|
+
| ---------------- | --------------------- | --------------------------------------------- |
|
|
673
|
+
| `selectDataList` | `Array<IDropDown>` | Dropdown options (required) |
|
|
674
|
+
| `optionLabel` | `string` | Label field name (required) |
|
|
675
|
+
| `optionValue` | `string` | Value field name (required) |
|
|
676
|
+
| `isEditMode` | `boolean` | Enable/disable editing (required) |
|
|
677
|
+
| `isLoading` | `boolean` | Loading state (required) |
|
|
678
|
+
| `total` | `number \| undefined` | Total items for pagination |
|
|
679
|
+
| `pagination` | `IPagination` | Current pagination state |
|
|
680
|
+
| `placeHolder` | `string` | Placeholder text (default: `'Select Option'`) |
|
|
572
681
|
|
|
573
682
|
**Model:** `value` - Two-way bound selected value (`string | null`)
|
|
574
683
|
|
|
@@ -585,19 +694,7 @@ Multi-select dropdown with lazy loading, search, select-all, and scroll paginati
|
|
|
585
694
|
```typescript
|
|
586
695
|
@Component({
|
|
587
696
|
imports: [LazyMultiSelectComponent],
|
|
588
|
-
template: `
|
|
589
|
-
<lib-lazy-multi-select
|
|
590
|
-
[(value)]="selectedIds"
|
|
591
|
-
[selectDataList]="items()"
|
|
592
|
-
[isEditMode]="true"
|
|
593
|
-
[isLoading]="loading()"
|
|
594
|
-
[total]="total()"
|
|
595
|
-
[pagination]="pagination()"
|
|
596
|
-
[placeHolder]="'Select items'"
|
|
597
|
-
(onSearch)="handleSearch($event)"
|
|
598
|
-
(onPagination)="handlePagination($event)"
|
|
599
|
-
/>
|
|
600
|
-
`,
|
|
697
|
+
template: ` <lib-lazy-multi-select [(value)]="selectedIds" [selectDataList]="items()" [isEditMode]="true" [isLoading]="loading()" [total]="total()" [pagination]="pagination()" [placeHolder]="'Select items'" (onSearch)="handleSearch($event)" (onPagination)="handlePagination($event)" /> `,
|
|
601
698
|
})
|
|
602
699
|
export class MyComponent {
|
|
603
700
|
readonly selectedIds = signal<string[] | null>(null);
|
|
@@ -622,17 +719,10 @@ Single user selection with lazy loading. Uses `USER_PROVIDER` internally or acce
|
|
|
622
719
|
imports: [UserSelectComponent],
|
|
623
720
|
template: `
|
|
624
721
|
<!-- Simple usage - uses USER_PROVIDER internally -->
|
|
625
|
-
<lib-user-select
|
|
626
|
-
[(value)]="selectedUserId"
|
|
627
|
-
[isEditMode]="true"
|
|
628
|
-
/>
|
|
722
|
+
<lib-user-select [(value)]="selectedUserId" [isEditMode]="true" />
|
|
629
723
|
|
|
630
724
|
<!-- With custom loadUsers function -->
|
|
631
|
-
<lib-user-select
|
|
632
|
-
[(value)]="selectedUserId"
|
|
633
|
-
[isEditMode]="true"
|
|
634
|
-
[loadUsers]="customLoadUsers"
|
|
635
|
-
/>
|
|
725
|
+
<lib-user-select [(value)]="selectedUserId" [isEditMode]="true" [loadUsers]="customLoadUsers" />
|
|
636
726
|
`,
|
|
637
727
|
})
|
|
638
728
|
export class MyComponent {
|
|
@@ -642,14 +732,14 @@ export class MyComponent {
|
|
|
642
732
|
|
|
643
733
|
**Inputs:**
|
|
644
734
|
|
|
645
|
-
| Input
|
|
646
|
-
|
|
|
647
|
-
| `isEditMode`
|
|
648
|
-
| `placeHolder`
|
|
649
|
-
| `filterActive`
|
|
650
|
-
| `additionalFilters` | `Record<string, unknown>` | `{}`
|
|
651
|
-
| `pageSize`
|
|
652
|
-
| `loadUsers`
|
|
735
|
+
| Input | Type | Default | Description |
|
|
736
|
+
| ------------------- | ------------------------- | --------------- | ---------------------------- |
|
|
737
|
+
| `isEditMode` | `boolean` | required | Enable/disable editing |
|
|
738
|
+
| `placeHolder` | `string` | `'Select User'` | Placeholder text |
|
|
739
|
+
| `filterActive` | `boolean` | `true` | Filter active users only |
|
|
740
|
+
| `additionalFilters` | `Record<string, unknown>` | `{}` | Extra filter params |
|
|
741
|
+
| `pageSize` | `number` | `20` | Page size for pagination |
|
|
742
|
+
| `loadUsers` | `LoadUsersFn` | - | Custom user loading function |
|
|
653
743
|
|
|
654
744
|
**Model:** `value` - Two-way bound selected user ID (`string | null`)
|
|
655
745
|
|
|
@@ -665,12 +755,7 @@ Multiple user selection with lazy loading. Uses `USER_PROVIDER` internally or ac
|
|
|
665
755
|
```typescript
|
|
666
756
|
@Component({
|
|
667
757
|
imports: [UserMultiSelectComponent],
|
|
668
|
-
template: `
|
|
669
|
-
<lib-user-multi-select
|
|
670
|
-
[(value)]="selectedUserIds"
|
|
671
|
-
[isEditMode]="true"
|
|
672
|
-
/>
|
|
673
|
-
`,
|
|
758
|
+
template: ` <lib-user-multi-select [(value)]="selectedUserIds" [isEditMode]="true" /> `,
|
|
674
759
|
})
|
|
675
760
|
export class MyComponent {
|
|
676
761
|
readonly selectedUserIds = signal<string[] | null>(null);
|
|
@@ -694,44 +779,32 @@ Drag & drop file upload with type filtering. Pass your own `uploadFile` function
|
|
|
694
779
|
imports: [FileUploaderComponent],
|
|
695
780
|
template: `
|
|
696
781
|
<!-- Single image upload -->
|
|
697
|
-
<lib-file-uploader
|
|
698
|
-
[uploadFile]="uploadFile"
|
|
699
|
-
[acceptTypes]="['image/*']"
|
|
700
|
-
[multiple]="false"
|
|
701
|
-
(fileUploaded)="onFileUploaded($event)"
|
|
702
|
-
/>
|
|
782
|
+
<lib-file-uploader [uploadFile]="uploadFile" [acceptTypes]="['image/*']" [multiple]="false" (fileUploaded)="onFileUploaded($event)" />
|
|
703
783
|
|
|
704
784
|
<!-- Multiple document upload -->
|
|
705
|
-
<lib-file-uploader
|
|
706
|
-
[uploadFile]="uploadFile"
|
|
707
|
-
[acceptTypes]="FILE_TYPE_FILTERS.DOCUMENTS"
|
|
708
|
-
[multiple]="true"
|
|
709
|
-
[maxFiles]="5"
|
|
710
|
-
(filesUploaded)="onFilesUploaded($event)"
|
|
711
|
-
/>
|
|
785
|
+
<lib-file-uploader [uploadFile]="uploadFile" [acceptTypes]="FILE_TYPE_FILTERS.DOCUMENTS" [multiple]="true" [maxFiles]="5" (filesUploaded)="onFilesUploaded($event)" />
|
|
712
786
|
`,
|
|
713
787
|
})
|
|
714
788
|
export class MyComponent {
|
|
715
789
|
readonly uploadService = inject(UploadService);
|
|
716
790
|
|
|
717
|
-
readonly uploadFile: UploadFileFn = (file, options) =>
|
|
718
|
-
this.uploadService.uploadSingleFile(file, options);
|
|
791
|
+
readonly uploadFile: UploadFileFn = (file, options) => this.uploadService.uploadSingleFile(file, options);
|
|
719
792
|
}
|
|
720
793
|
```
|
|
721
794
|
|
|
722
795
|
**Inputs:**
|
|
723
796
|
|
|
724
|
-
| Input
|
|
725
|
-
|
|
|
726
|
-
| `uploadFile`
|
|
727
|
-
| `acceptTypes`
|
|
728
|
-
| `multiple`
|
|
729
|
-
| `maxFiles`
|
|
730
|
-
| `maxSizeMb`
|
|
731
|
-
| `uploadOptions` | `IFileUploadOptions` | `{}`
|
|
732
|
-
| `disabled`
|
|
733
|
-
| `showPreview`
|
|
734
|
-
| `autoUpload`
|
|
797
|
+
| Input | Type | Default | Description |
|
|
798
|
+
| --------------- | -------------------- | -------- | ------------------------------- |
|
|
799
|
+
| `uploadFile` | `UploadFileFn` | required | Upload function |
|
|
800
|
+
| `acceptTypes` | `string[]` | `[]` | Allowed MIME types |
|
|
801
|
+
| `multiple` | `boolean` | `false` | Allow multiple files |
|
|
802
|
+
| `maxFiles` | `number` | `10` | Max files for multiple |
|
|
803
|
+
| `maxSizeMb` | `number` | `10` | Max file size in MB |
|
|
804
|
+
| `uploadOptions` | `IFileUploadOptions` | `{}` | Upload options |
|
|
805
|
+
| `disabled` | `boolean` | `false` | Disable uploader |
|
|
806
|
+
| `showPreview` | `boolean` | `true` | Show selected files preview |
|
|
807
|
+
| `autoUpload` | `boolean` | `true` | Upload immediately on selection |
|
|
735
808
|
|
|
736
809
|
**Outputs:** `fileUploaded` (IUploadedFile), `filesUploaded` (IUploadedFile[]), `onError` (Error), `fileSelected` (File[])
|
|
737
810
|
|
|
@@ -744,15 +817,7 @@ Dialog to browse and select existing files with filtering.
|
|
|
744
817
|
```typescript
|
|
745
818
|
@Component({
|
|
746
819
|
imports: [FileSelectorDialogComponent],
|
|
747
|
-
template: `
|
|
748
|
-
<lib-file-selector-dialog
|
|
749
|
-
[(visible)]="showFileSelector"
|
|
750
|
-
[loadFiles]="loadFiles"
|
|
751
|
-
[acceptTypes]="['image/*']"
|
|
752
|
-
[multiple]="false"
|
|
753
|
-
(fileSelected)="onFileSelected($event)"
|
|
754
|
-
/>
|
|
755
|
-
`,
|
|
820
|
+
template: ` <lib-file-selector-dialog [(visible)]="showFileSelector" [loadFiles]="loadFiles" [acceptTypes]="['image/*']" [multiple]="false" (fileSelected)="onFileSelected($event)" /> `,
|
|
756
821
|
})
|
|
757
822
|
export class MyComponent {
|
|
758
823
|
readonly showFileSelector = signal(false);
|
|
@@ -767,14 +832,14 @@ export class MyComponent {
|
|
|
767
832
|
|
|
768
833
|
**Inputs:**
|
|
769
834
|
|
|
770
|
-
| Input
|
|
771
|
-
|
|
|
772
|
-
| `loadFiles`
|
|
773
|
-
| `header`
|
|
774
|
-
| `acceptTypes`
|
|
775
|
-
| `multiple`
|
|
776
|
-
| `maxSelection` | `number`
|
|
777
|
-
| `pageSize`
|
|
835
|
+
| Input | Type | Default | Description |
|
|
836
|
+
| -------------- | ------------- | --------------- | ------------------------ |
|
|
837
|
+
| `loadFiles` | `LoadFilesFn` | required | File loading function |
|
|
838
|
+
| `header` | `string` | `'Select File'` | Dialog header |
|
|
839
|
+
| `acceptTypes` | `string[]` | `[]` | Allowed MIME types |
|
|
840
|
+
| `multiple` | `boolean` | `false` | Allow multiple selection |
|
|
841
|
+
| `maxSelection` | `number` | `10` | Max files for multiple |
|
|
842
|
+
| `pageSize` | `number` | `20` | Page size for pagination |
|
|
778
843
|
|
|
779
844
|
**Model:** `visible` - Two-way bound dialog visibility (`boolean`)
|
|
780
845
|
|
|
@@ -782,7 +847,7 @@ export class MyComponent {
|
|
|
782
847
|
|
|
783
848
|
---
|
|
784
849
|
|
|
785
|
-
##
|
|
850
|
+
## 6. Directives
|
|
786
851
|
|
|
787
852
|
### HasPermissionDirective
|
|
788
853
|
|
|
@@ -792,7 +857,7 @@ Structural directive for permission-based rendering. Fail-closed: hides content
|
|
|
792
857
|
- **Input:** `hasPermission` - `string | ILogicNode | null`
|
|
793
858
|
|
|
794
859
|
```typescript
|
|
795
|
-
import { HasPermissionDirective, ILogicNode } from
|
|
860
|
+
import { HasPermissionDirective, ILogicNode } from "@flusys/ng-shared";
|
|
796
861
|
|
|
797
862
|
@Component({
|
|
798
863
|
imports: [HasPermissionDirective],
|
|
@@ -806,11 +871,11 @@ import { HasPermissionDirective, ILogicNode } from '@flusys/ng-shared';
|
|
|
806
871
|
})
|
|
807
872
|
export class MyComponent {
|
|
808
873
|
readonly editLogic: ILogicNode = {
|
|
809
|
-
type:
|
|
810
|
-
operator:
|
|
874
|
+
type: "group",
|
|
875
|
+
operator: "AND",
|
|
811
876
|
children: [
|
|
812
|
-
{ type:
|
|
813
|
-
{ type:
|
|
877
|
+
{ type: "action", actionId: "user.view" },
|
|
878
|
+
{ type: "action", actionId: "user.update" },
|
|
814
879
|
],
|
|
815
880
|
};
|
|
816
881
|
}
|
|
@@ -824,8 +889,7 @@ Toggles readonly/disabled state for form controls based on edit mode. Supports `
|
|
|
824
889
|
- **Input:** `isEditMode` (required boolean)
|
|
825
890
|
|
|
826
891
|
```html
|
|
827
|
-
<input [appEditModeElementChanger] [isEditMode]="isEditing()" />
|
|
828
|
-
<p-select [appEditModeElementChanger] [isEditMode]="isEditing()" />
|
|
892
|
+
<input [appEditModeElementChanger] [isEditMode]="isEditing()" /> <p-select [appEditModeElementChanger] [isEditMode]="isEditing()" />
|
|
829
893
|
```
|
|
830
894
|
|
|
831
895
|
### IsEmptyImageDirective
|
|
@@ -850,13 +914,12 @@ Prevents default browser behavior on specified events and emits the event.
|
|
|
850
914
|
- **Output:** `action` - Emits the prevented event
|
|
851
915
|
|
|
852
916
|
```html
|
|
853
|
-
<a href="#" appPreventDefault (action)="handleClick($event)">Click me</a>
|
|
854
|
-
<input appPreventDefault eventType="keydown" preventKey="Enter" (action)="onEnter($event)" />
|
|
917
|
+
<a href="#" appPreventDefault (action)="handleClick($event)">Click me</a> <input appPreventDefault eventType="keydown" preventKey="Enter" (action)="onEnter($event)" />
|
|
855
918
|
```
|
|
856
919
|
|
|
857
920
|
---
|
|
858
921
|
|
|
859
|
-
##
|
|
922
|
+
## 7. Guards
|
|
860
923
|
|
|
861
924
|
Route-level guards for permission-based access control. All guards deny access when permissions are not loaded (fail-closed).
|
|
862
925
|
|
|
@@ -865,24 +928,29 @@ Route-level guards for permission-based access control. All guards deny access w
|
|
|
865
928
|
Single permission or complex logic check.
|
|
866
929
|
|
|
867
930
|
```typescript
|
|
868
|
-
import { permissionGuard } from
|
|
931
|
+
import { permissionGuard } from "@flusys/ng-shared";
|
|
869
932
|
|
|
870
933
|
const routes: Routes = [
|
|
871
934
|
// Simple permission
|
|
872
|
-
{ path:
|
|
935
|
+
{ path: "users", canActivate: [permissionGuard("user.view")] },
|
|
873
936
|
|
|
874
937
|
// Complex logic (ILogicNode)
|
|
875
|
-
{
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
938
|
+
{
|
|
939
|
+
path: "admin",
|
|
940
|
+
canActivate: [
|
|
941
|
+
permissionGuard({
|
|
942
|
+
type: "group",
|
|
943
|
+
operator: "AND",
|
|
944
|
+
children: [
|
|
945
|
+
{ type: "action", actionId: "admin.view" },
|
|
946
|
+
{ type: "action", actionId: "admin.manage" },
|
|
947
|
+
],
|
|
948
|
+
}),
|
|
881
949
|
],
|
|
882
|
-
}
|
|
950
|
+
},
|
|
883
951
|
|
|
884
952
|
// Custom redirect on deny
|
|
885
|
-
{ path:
|
|
953
|
+
{ path: "settings", canActivate: [permissionGuard("settings.view", "/access-denied")] },
|
|
886
954
|
];
|
|
887
955
|
```
|
|
888
956
|
|
|
@@ -904,42 +972,103 @@ AND logic - allows access only if user has ALL specified permissions.
|
|
|
904
972
|
|
|
905
973
|
---
|
|
906
974
|
|
|
907
|
-
##
|
|
975
|
+
## 8. Utilities
|
|
908
976
|
|
|
909
977
|
### Permission Evaluator
|
|
910
978
|
|
|
911
|
-
Pure functions for permission logic evaluation. Used internally by `HasPermissionDirective` and guards.
|
|
979
|
+
Pure functions for permission logic evaluation. Used internally by `HasPermissionDirective` and guards. **Supports wildcard permissions.**
|
|
912
980
|
|
|
913
981
|
```typescript
|
|
914
|
-
import { evaluatePermission, evaluateLogicNode, hasAnyPermission, hasAllPermissions } from
|
|
982
|
+
import { evaluatePermission, evaluateLogicNode, hasAnyPermission, hasAllPermissions, hasPermission } from "@flusys/ng-shared";
|
|
983
|
+
|
|
984
|
+
const userPermissions = ["user.view", "user.create", "admin.*"];
|
|
915
985
|
|
|
916
|
-
|
|
986
|
+
// Low-level hasPermission check with wildcard support
|
|
987
|
+
hasPermission("user.view", userPermissions); // true (exact match)
|
|
988
|
+
hasPermission("admin.manage", userPermissions); // true (matches 'admin.*')
|
|
989
|
+
hasPermission("settings.view", ["*"]); // true (global wildcard)
|
|
917
990
|
|
|
918
991
|
// Evaluate string or ILogicNode
|
|
919
|
-
evaluatePermission(
|
|
920
|
-
evaluatePermission(null, userPermissions);
|
|
992
|
+
evaluatePermission("user.view", userPermissions); // true
|
|
993
|
+
evaluatePermission(null, userPermissions); // false
|
|
921
994
|
|
|
922
995
|
// Evaluate ILogicNode tree recursively
|
|
923
996
|
evaluateLogicNode(logicNode, userPermissions);
|
|
924
997
|
|
|
925
|
-
// Simple OR/AND checks
|
|
926
|
-
hasAnyPermission([
|
|
927
|
-
hasAllPermissions([
|
|
998
|
+
// Simple OR/AND checks (also support wildcards)
|
|
999
|
+
hasAnyPermission(["user.view", "user.delete"], userPermissions); // true (has user.view)
|
|
1000
|
+
hasAllPermissions(["user.view", "user.delete"], userPermissions); // false (missing user.delete)
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
**Wildcard Rules:**
|
|
1004
|
+
|
|
1005
|
+
| Pattern | Matches |
|
|
1006
|
+
| ----------- | --------------------------------------- |
|
|
1007
|
+
| `*` | All permissions (global wildcard) |
|
|
1008
|
+
| `module.*` | All permissions starting with `module.` |
|
|
1009
|
+
| `user.read` | Exact match only |
|
|
1010
|
+
|
|
1011
|
+
**Implementation Details:**
|
|
1012
|
+
|
|
1013
|
+
```typescript
|
|
1014
|
+
export function hasPermission(requiredPermission: string, userPermissions: string[]): boolean {
|
|
1015
|
+
// Exact match
|
|
1016
|
+
if (userPermissions.includes(requiredPermission)) return true;
|
|
1017
|
+
|
|
1018
|
+
// Wildcard matching
|
|
1019
|
+
for (const permission of userPermissions) {
|
|
1020
|
+
// Global wildcard
|
|
1021
|
+
if (permission === "*") return true;
|
|
1022
|
+
|
|
1023
|
+
// Module wildcard (e.g., 'user.*' matches 'user.read')
|
|
1024
|
+
if (permission.endsWith(".*")) {
|
|
1025
|
+
const prefix = permission.slice(0, -1); // 'user.'
|
|
1026
|
+
if (requiredPermission.startsWith(prefix)) return true;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
### Scroll Pagination
|
|
1035
|
+
|
|
1036
|
+
Utility for lazy-loading dropdowns with scroll detection.
|
|
1037
|
+
|
|
1038
|
+
```typescript
|
|
1039
|
+
import { checkScrollPagination, ScrollPaginationConfig } from '@flusys/ng-shared';
|
|
1040
|
+
|
|
1041
|
+
// In a component
|
|
1042
|
+
onScroll(event: Event): void {
|
|
1043
|
+
const nextPagination = checkScrollPagination(event, {
|
|
1044
|
+
pagination: this.pagination(),
|
|
1045
|
+
total: this.total(),
|
|
1046
|
+
isLoading: this.isLoading(),
|
|
1047
|
+
threshold: 50, // pixels from bottom (default: 50)
|
|
1048
|
+
});
|
|
1049
|
+
if (nextPagination) {
|
|
1050
|
+
this.onPagination.emit(nextPagination);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
928
1053
|
```
|
|
929
1054
|
|
|
1055
|
+
**Interface:** `ScrollPaginationConfig` - `{ threshold?, pagination, total, isLoading }`
|
|
1056
|
+
|
|
1057
|
+
**Returns:** `IPagination | null` - Next page pagination or null if not needed.
|
|
1058
|
+
|
|
930
1059
|
---
|
|
931
1060
|
|
|
932
|
-
##
|
|
1061
|
+
## 9. Classes
|
|
933
1062
|
|
|
934
1063
|
### BaseFormControl
|
|
935
1064
|
|
|
936
1065
|
Abstract base class for custom form controls. Implements both `ControlValueAccessor` (reactive forms) and `FormValueControl` (signal forms).
|
|
937
1066
|
|
|
938
1067
|
```typescript
|
|
939
|
-
import { BaseFormControl, provideValueAccessor } from
|
|
1068
|
+
import { BaseFormControl, provideValueAccessor } from "@flusys/ng-shared";
|
|
940
1069
|
|
|
941
1070
|
@Component({
|
|
942
|
-
selector:
|
|
1071
|
+
selector: "my-select",
|
|
943
1072
|
providers: [provideValueAccessor(MySelectComponent)],
|
|
944
1073
|
})
|
|
945
1074
|
export class MySelectComponent extends BaseFormControl<string | null> {
|
|
@@ -965,17 +1094,87 @@ export class MySelectComponent extends BaseFormControl<string | null> {
|
|
|
965
1094
|
|
|
966
1095
|
**Helper:** `provideValueAccessor(ComponentClass)` - Factory for `NG_VALUE_ACCESSOR` provider
|
|
967
1096
|
|
|
1097
|
+
### BaseFormPage
|
|
1098
|
+
|
|
1099
|
+
Abstract directive for form page components (create/edit).
|
|
1100
|
+
|
|
1101
|
+
```typescript
|
|
1102
|
+
import { BaseFormPage } from '@flusys/ng-shared';
|
|
1103
|
+
|
|
1104
|
+
@Component({ ... })
|
|
1105
|
+
export class ProductFormComponent extends BaseFormPage<IProduct, IProductFormModel> {
|
|
1106
|
+
private readonly productService = inject(ProductApiService);
|
|
1107
|
+
private readonly _formModel = signal<IProductFormModel>({ name: '', price: 0 });
|
|
1108
|
+
readonly formModel = this._formModel.asReadonly();
|
|
1109
|
+
|
|
1110
|
+
getFormModel(): Signal<IProductFormModel> { return this.formModel; }
|
|
1111
|
+
getResourceRoute(): string { return '/products'; }
|
|
1112
|
+
getResourceName(): string { return 'Product'; }
|
|
1113
|
+
isFormValid(): boolean { return this.formModel().name.trim().length > 0; }
|
|
1114
|
+
|
|
1115
|
+
loadItem(id: string): void {
|
|
1116
|
+
this.productService.findById(id).subscribe(res => {
|
|
1117
|
+
if (res.success && res.data) {
|
|
1118
|
+
this.existingItem.set(res.data);
|
|
1119
|
+
this._formModel.set({ name: res.data.name, price: res.data.price });
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
createItem(model: IProductFormModel): Observable<unknown> {
|
|
1125
|
+
return this.productService.insert(model);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
updateItem(model: IProductFormModel): Observable<unknown> {
|
|
1129
|
+
return this.productService.update({ id: this.existingItem()!.id, ...model });
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
**Signals:** `isLoading`, `existingItem`, `isEditMode` (computed)
|
|
1135
|
+
|
|
1136
|
+
**Methods:** `onSubmit()`, `onCancel()`, `showSuccess()`, `showError()`, `showValidationError()`
|
|
1137
|
+
|
|
1138
|
+
### BaseListPage
|
|
1139
|
+
|
|
1140
|
+
Abstract directive for list page components with pagination and CRUD operations.
|
|
1141
|
+
|
|
1142
|
+
```typescript
|
|
1143
|
+
import { BaseListPage } from '@flusys/ng-shared';
|
|
1144
|
+
|
|
1145
|
+
@Component({ ... })
|
|
1146
|
+
export class UserListComponent extends BaseListPage<IUser> {
|
|
1147
|
+
private readonly userService = inject(UserApiService);
|
|
1148
|
+
|
|
1149
|
+
getResourceRoute(): string { return '/users'; }
|
|
1150
|
+
getDeleteConfirmMessage(user: IUser): string { return `Delete "${user.name}"?`; }
|
|
1151
|
+
|
|
1152
|
+
async loadData(): Promise<void> {
|
|
1153
|
+
this.isLoading.set(true);
|
|
1154
|
+
const res = await this.userService.findByIdAsync(...);
|
|
1155
|
+
this.items.set(res.data ?? []);
|
|
1156
|
+
this.total.set(res.meta?.total ?? 0);
|
|
1157
|
+
this.isLoading.set(false);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
**Signals:** `items`, `isLoading`, `total`, `pageSize`, `first`, `currentPage` (computed), `showCompanyInfo` (computed)
|
|
1163
|
+
|
|
1164
|
+
**Methods:** `onCreate()`, `onEdit(id)`, `onPageChange(event)`, `onDelete()`, `onDeleteAsync()`, `showSuccess()`, `showError()`, `showInfo()`, `showWarn()`
|
|
1165
|
+
|
|
968
1166
|
---
|
|
969
1167
|
|
|
970
|
-
##
|
|
1168
|
+
## 10. Modules
|
|
971
1169
|
|
|
972
1170
|
### AngularModule
|
|
973
1171
|
|
|
974
1172
|
Re-exports common Angular modules for convenience.
|
|
975
1173
|
|
|
976
1174
|
```typescript
|
|
977
|
-
import { AngularModule } from
|
|
978
|
-
// Includes: CommonModule, FormsModule, ReactiveFormsModule, RouterLink, RouterOutlet,
|
|
1175
|
+
import { AngularModule } from "@flusys/ng-shared";
|
|
1176
|
+
// Includes: CommonModule, FormsModule, ReactiveFormsModule, RouterLink, RouterOutlet,
|
|
1177
|
+
// RouterLinkActive, NgOptimizedImage, NgComponentOutlet, + directives (IsEmptyImageDirective, PreventDefaultDirective)
|
|
979
1178
|
// Providers: DatePipe
|
|
980
1179
|
```
|
|
981
1180
|
|
|
@@ -984,14 +1183,22 @@ import { AngularModule } from '@flusys/ng-shared';
|
|
|
984
1183
|
Re-exports PrimeNG component modules for convenience.
|
|
985
1184
|
|
|
986
1185
|
```typescript
|
|
987
|
-
import { PrimeModule } from
|
|
988
|
-
// Includes
|
|
989
|
-
//
|
|
1186
|
+
import { PrimeModule } from "@flusys/ng-shared";
|
|
1187
|
+
// Includes 30 modules:
|
|
1188
|
+
// - Layout: AccordionModule, CardModule, DividerModule, PanelModule, SplitterModule, TabsModule, ToolbarModule
|
|
1189
|
+
// - Form: AutoCompleteModule, CheckboxModule, DatePickerModule, InputTextModule, InputTextareaModule,
|
|
1190
|
+
// MultiSelectModule, RadioButtonModule, SelectModule, ToggleSwitchModule
|
|
1191
|
+
// - Button: ButtonModule, SpeedDialModule, SplitButtonModule
|
|
1192
|
+
// - Data: PaginatorModule, TableModule, TreeTableModule
|
|
1193
|
+
// - Overlay: DialogModule, DrawerModule, PopoverModule, TooltipModule
|
|
1194
|
+
// - File: FileUploadModule
|
|
1195
|
+
// - Media: ImageModule
|
|
1196
|
+
// - Misc: BadgeModule, TagModule
|
|
990
1197
|
```
|
|
991
1198
|
|
|
992
1199
|
---
|
|
993
1200
|
|
|
994
|
-
##
|
|
1201
|
+
## 11. Provider Interfaces (Package Independence)
|
|
995
1202
|
|
|
996
1203
|
ng-shared defines **provider interfaces** to enable feature packages (ng-iam, ng-storage) to access auth functionality without direct dependencies.
|
|
997
1204
|
|
|
@@ -1012,50 +1219,65 @@ ng-iam/ng-storage (consume interfaces via DI)
|
|
|
1012
1219
|
User list access for IAM user selection.
|
|
1013
1220
|
|
|
1014
1221
|
```typescript
|
|
1015
|
-
interface IUserBasicInfo {
|
|
1222
|
+
interface IUserBasicInfo {
|
|
1223
|
+
id: string;
|
|
1224
|
+
name: string;
|
|
1225
|
+
email: string;
|
|
1226
|
+
}
|
|
1016
1227
|
|
|
1017
1228
|
interface IUserProvider {
|
|
1018
|
-
getUsers(filter?: {
|
|
1019
|
-
page?: number; pageSize?: number; search?: string;
|
|
1020
|
-
companyId?: string; branchId?: string;
|
|
1021
|
-
}): Observable<IListResponse<IUserBasicInfo>>;
|
|
1229
|
+
getUsers(filter?: { page?: number; pageSize?: number; search?: string; companyId?: string; branchId?: string }): Observable<IListResponse<IUserBasicInfo>>;
|
|
1022
1230
|
}
|
|
1023
1231
|
```
|
|
1024
1232
|
|
|
1233
|
+
**Token Error Message:** `'USER_PROVIDER not configured. Please provide an implementation in app.config.ts'`
|
|
1234
|
+
|
|
1025
1235
|
#### ICompanyApiProvider / `COMPANY_API_PROVIDER`
|
|
1026
1236
|
|
|
1027
1237
|
Company list access for IAM company selection.
|
|
1028
1238
|
|
|
1029
1239
|
```typescript
|
|
1030
|
-
interface ICompanyBasicInfo {
|
|
1240
|
+
interface ICompanyBasicInfo {
|
|
1241
|
+
id: string;
|
|
1242
|
+
name: string;
|
|
1243
|
+
slug?: string;
|
|
1244
|
+
}
|
|
1031
1245
|
|
|
1032
1246
|
interface ICompanyApiProvider {
|
|
1033
|
-
getCompanies(filter?: {
|
|
1034
|
-
page?: number; pageSize?: number; search?: string;
|
|
1035
|
-
}): Observable<IListResponse<ICompanyBasicInfo>>;
|
|
1247
|
+
getCompanies(filter?: { page?: number; pageSize?: number; search?: string }): Observable<IListResponse<ICompanyBasicInfo>>;
|
|
1036
1248
|
}
|
|
1037
1249
|
```
|
|
1038
1250
|
|
|
1251
|
+
**Token Error Message:** `'COMPANY_API_PROVIDER not configured. Please provide an implementation in app.config.ts'`
|
|
1252
|
+
|
|
1039
1253
|
#### IUserPermissionProvider / `USER_PERMISSION_PROVIDER`
|
|
1040
1254
|
|
|
1041
1255
|
User permission queries for IAM.
|
|
1042
1256
|
|
|
1043
1257
|
```typescript
|
|
1258
|
+
interface IUserBranchPermission {
|
|
1259
|
+
branchId: string;
|
|
1260
|
+
branchName: string;
|
|
1261
|
+
permissions: string[];
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1044
1264
|
interface IUserPermissionProvider {
|
|
1045
|
-
getUserBranchPermissions(userId: string): Observable<ISingleResponse<
|
|
1265
|
+
getUserBranchPermissions(userId: string): Observable<ISingleResponse<IUserBranchPermission[]>>;
|
|
1046
1266
|
}
|
|
1047
1267
|
```
|
|
1048
1268
|
|
|
1269
|
+
**Token Error Message:** `'USER_PERMISSION_PROVIDER not configured. Please provide an implementation in app.config.ts'`
|
|
1270
|
+
|
|
1049
1271
|
#### IProfileUploadProvider / `PROFILE_UPLOAD_PROVIDER`
|
|
1050
1272
|
|
|
1051
|
-
Profile picture upload for ng-auth profile page. Implemented by ng-storage.
|
|
1273
|
+
Profile picture upload for ng-auth profile page. Implemented by ng-storage. **Optional token** - use with `inject(..., { optional: true })`.
|
|
1052
1274
|
|
|
1053
1275
|
```typescript
|
|
1054
1276
|
interface IProfileUploadResult {
|
|
1055
|
-
id: string;
|
|
1056
|
-
name: string;
|
|
1057
|
-
key: string;
|
|
1058
|
-
size: number;
|
|
1277
|
+
id: string; // File manager ID (UUID)
|
|
1278
|
+
name: string; // Original file name
|
|
1279
|
+
key: string; // Storage key/path
|
|
1280
|
+
size: number; // File size in bytes
|
|
1059
1281
|
contentType: string; // MIME type
|
|
1060
1282
|
}
|
|
1061
1283
|
|
|
@@ -1067,16 +1289,13 @@ interface IProfileUploadOptions {
|
|
|
1067
1289
|
}
|
|
1068
1290
|
|
|
1069
1291
|
interface IProfileUploadProvider {
|
|
1070
|
-
uploadProfilePicture(
|
|
1071
|
-
file: File,
|
|
1072
|
-
options?: IProfileUploadOptions
|
|
1073
|
-
): Observable<ISingleResponse<IProfileUploadResult>>;
|
|
1292
|
+
uploadProfilePicture(file: File, options?: IProfileUploadOptions): Observable<ISingleResponse<IProfileUploadResult>>;
|
|
1074
1293
|
}
|
|
1075
1294
|
```
|
|
1076
1295
|
|
|
1077
1296
|
#### IProfilePermissionProvider / `PROFILE_PERMISSION_PROVIDER`
|
|
1078
1297
|
|
|
1079
|
-
User permission queries for ng-auth profile page. Implemented by ng-iam.
|
|
1298
|
+
User permission queries for ng-auth profile page. Implemented by ng-iam. **Optional token** - use with `inject(..., { optional: true })`.
|
|
1080
1299
|
|
|
1081
1300
|
```typescript
|
|
1082
1301
|
interface IProfileRoleInfo {
|
|
@@ -1112,6 +1331,8 @@ interface IAuthStateProvider {
|
|
|
1112
1331
|
}
|
|
1113
1332
|
```
|
|
1114
1333
|
|
|
1334
|
+
**Token Error Message:** `'AUTH_STATE_PROVIDER not configured. Please provide an implementation in app.config.ts'`
|
|
1335
|
+
|
|
1115
1336
|
**Usage:**
|
|
1116
1337
|
|
|
1117
1338
|
```typescript
|
|
@@ -1133,7 +1354,7 @@ export class PublicFormComponent {
|
|
|
1133
1354
|
|
|
1134
1355
|
#### IUserListProvider / `USER_LIST_PROVIDER`
|
|
1135
1356
|
|
|
1136
|
-
Extends user list pages with extra columns, actions, and data enrichment. Optional
|
|
1357
|
+
Extends user list pages with extra columns, actions, and data enrichment. **Optional token** - use with `inject(..., { optional: true })`.
|
|
1137
1358
|
|
|
1138
1359
|
```typescript
|
|
1139
1360
|
interface IUserListItem {
|
|
@@ -1142,14 +1363,13 @@ interface IUserListItem {
|
|
|
1142
1363
|
email: string;
|
|
1143
1364
|
phone?: string;
|
|
1144
1365
|
isActive?: boolean;
|
|
1145
|
-
[key: string]: unknown;
|
|
1146
1366
|
}
|
|
1147
1367
|
|
|
1148
1368
|
interface IUserListAction<T = IUserListItem> {
|
|
1149
1369
|
id: string;
|
|
1150
1370
|
label: string;
|
|
1151
1371
|
icon?: string;
|
|
1152
|
-
severity?:
|
|
1372
|
+
severity?: "primary" | "secondary" | "success" | "info" | "warn" | "danger";
|
|
1153
1373
|
permission?: string;
|
|
1154
1374
|
tooltip?: string;
|
|
1155
1375
|
disabled?: boolean | ((user: T) => boolean);
|
|
@@ -1161,7 +1381,16 @@ interface IUserListColumn {
|
|
|
1161
1381
|
header: string;
|
|
1162
1382
|
width?: string;
|
|
1163
1383
|
sortable?: boolean;
|
|
1164
|
-
templateType?:
|
|
1384
|
+
templateType?: "text" | "badge" | "date" | "boolean" | "custom";
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
interface IUserListFilter {
|
|
1388
|
+
page?: number;
|
|
1389
|
+
pageSize?: number;
|
|
1390
|
+
search?: string;
|
|
1391
|
+
isActive?: boolean;
|
|
1392
|
+
companyId?: string;
|
|
1393
|
+
branchId?: string;
|
|
1165
1394
|
}
|
|
1166
1395
|
|
|
1167
1396
|
interface IUserListProvider<T extends IUserListItem = IUserListItem> {
|
|
@@ -1178,17 +1407,13 @@ interface IUserListProvider<T extends IUserListItem = IUserListItem> {
|
|
|
1178
1407
|
|
|
1179
1408
|
```typescript
|
|
1180
1409
|
// In app.config.ts
|
|
1181
|
-
providers: [
|
|
1182
|
-
{ provide: USER_LIST_PROVIDER, useClass: MyUserListProvider },
|
|
1183
|
-
]
|
|
1410
|
+
providers: [{ provide: USER_LIST_PROVIDER, useClass: MyUserListProvider }];
|
|
1184
1411
|
|
|
1185
1412
|
// Implementation
|
|
1186
|
-
@Injectable({ providedIn:
|
|
1413
|
+
@Injectable({ providedIn: "root" })
|
|
1187
1414
|
export class MyUserListProvider implements IUserListProvider {
|
|
1188
1415
|
getExtraRowActions() {
|
|
1189
|
-
return [
|
|
1190
|
-
{ id: 'assign-role', label: 'Assign Role', icon: 'pi pi-users' },
|
|
1191
|
-
];
|
|
1416
|
+
return [{ id: "assign-role", label: "Assign Role", icon: "pi pi-users" }];
|
|
1192
1417
|
}
|
|
1193
1418
|
}
|
|
1194
1419
|
```
|
|
@@ -1196,13 +1421,13 @@ export class MyUserListProvider implements IUserListProvider {
|
|
|
1196
1421
|
### Usage in Consuming Packages
|
|
1197
1422
|
|
|
1198
1423
|
```typescript
|
|
1199
|
-
import { USER_PROVIDER } from
|
|
1424
|
+
import { USER_PROVIDER } from "@flusys/ng-shared";
|
|
1200
1425
|
|
|
1201
1426
|
export class UserSelectorComponent {
|
|
1202
1427
|
private readonly userProvider = inject(USER_PROVIDER);
|
|
1203
1428
|
|
|
1204
1429
|
loadUsers() {
|
|
1205
|
-
this.userProvider.getUsers({ page: 0, pageSize: 50 }).subscribe(response => {
|
|
1430
|
+
this.userProvider.getUsers({ page: 0, pageSize: 50 }).subscribe((response) => {
|
|
1206
1431
|
this.users.set(response.data ?? []);
|
|
1207
1432
|
});
|
|
1208
1433
|
}
|
|
@@ -1213,7 +1438,7 @@ export class UserSelectorComponent {
|
|
|
1213
1438
|
|
|
1214
1439
|
```typescript
|
|
1215
1440
|
// app.config.ts
|
|
1216
|
-
import { provideAuthProviders } from
|
|
1441
|
+
import { provideAuthProviders } from "@flusys/ng-auth";
|
|
1217
1442
|
|
|
1218
1443
|
export const appConfig: ApplicationConfig = {
|
|
1219
1444
|
providers: [
|
|
@@ -1236,8 +1461,9 @@ export const appConfig: ApplicationConfig = {
|
|
|
1236
1461
|
|
|
1237
1462
|
- **Extend `ApiResourceService`** for new services (signal-based)
|
|
1238
1463
|
- Use reactive signals (`data`, `isLoading`, `total`) in templates
|
|
1239
|
-
- Use `fetchList()` to trigger queries, `reload()` to refresh
|
|
1464
|
+
- Use `fetchList()` to trigger queries (also initializes resource), `reload()` to refresh
|
|
1240
1465
|
- Use async methods (`insertAsync`, `updateAsync`) for one-off operations
|
|
1466
|
+
- Resource is lazy-initialized - no HTTP requests until first `fetchList()` or `initListResource()`
|
|
1241
1467
|
|
|
1242
1468
|
### File URLs
|
|
1243
1469
|
|
|
@@ -1263,6 +1489,7 @@ export const appConfig: ApplicationConfig = {
|
|
|
1263
1489
|
- Use permission guards for route-level access control
|
|
1264
1490
|
- Use `PermissionValidatorService` for programmatic checks in services
|
|
1265
1491
|
- Permissions follow fail-closed model: no access by default
|
|
1492
|
+
- Wildcards supported: `*` (all), `module.*` (module-scoped)
|
|
1266
1493
|
|
|
1267
1494
|
---
|
|
1268
1495
|
|
|
@@ -1298,89 +1525,142 @@ export const appConfig: ApplicationConfig = {
|
|
|
1298
1525
|
|
|
1299
1526
|
**Solution:** ng-shared must NEVER import from ng-layout. Move shared components to ng-shared, layout-specific components to ng-layout.
|
|
1300
1527
|
|
|
1528
|
+
### Provider Token Errors
|
|
1529
|
+
|
|
1530
|
+
**Problem:** `'XXX_PROVIDER not configured'` error at runtime.
|
|
1531
|
+
|
|
1532
|
+
**Solution:** Ensure the provider is registered in `app.config.ts`:
|
|
1533
|
+
|
|
1534
|
+
```typescript
|
|
1535
|
+
// Required providers
|
|
1536
|
+
providers: [
|
|
1537
|
+
{ provide: USER_PROVIDER, useClass: AuthUserProvider },
|
|
1538
|
+
{ provide: COMPANY_API_PROVIDER, useClass: AuthCompanyApiProvider },
|
|
1539
|
+
{ provide: USER_PERMISSION_PROVIDER, useClass: AuthUserPermissionProvider },
|
|
1540
|
+
{ provide: AUTH_STATE_PROVIDER, useClass: AuthStateProviderAdapter },
|
|
1541
|
+
];
|
|
1542
|
+
|
|
1543
|
+
// OR use the convenience function
|
|
1544
|
+
providers: [...provideAuthProviders()];
|
|
1545
|
+
```
|
|
1546
|
+
|
|
1547
|
+
### Permissions Not Working
|
|
1548
|
+
|
|
1549
|
+
**Problem:** `hasPermission()` returns false even though user should have access.
|
|
1550
|
+
|
|
1551
|
+
**Solution:**
|
|
1552
|
+
|
|
1553
|
+
1. Check if permissions are loaded: `permissionValidator.isLoaded()`
|
|
1554
|
+
2. Verify permission codes match exactly (case-sensitive)
|
|
1555
|
+
3. For wildcard access, ensure user has `*` or `module.*` in their permissions
|
|
1556
|
+
|
|
1301
1557
|
---
|
|
1302
1558
|
|
|
1303
1559
|
## API Reference
|
|
1304
1560
|
|
|
1305
1561
|
### Services
|
|
1306
1562
|
|
|
1307
|
-
| Service
|
|
1308
|
-
|
|
|
1309
|
-
| `ApiResourceService<DTO, T>`
|
|
1310
|
-
| `FileUrlService`
|
|
1311
|
-
| `PermissionValidatorService`
|
|
1312
|
-
| `CookieService`
|
|
1313
|
-
| `PlatformService`
|
|
1563
|
+
| Service | Description |
|
|
1564
|
+
| ---------------------------- | ---------------------------------------------------------------------------------------- |
|
|
1565
|
+
| `ApiResourceService<DTO, T>` | Signal-based CRUD with resource() API (lazy-initialized, accepts optional `serviceName`) |
|
|
1566
|
+
| `FileUrlService` | Cloud storage URL fetching |
|
|
1567
|
+
| `PermissionValidatorService` | Permission state management with wildcards |
|
|
1568
|
+
| `CookieService` | SSR-aware cookie reading |
|
|
1569
|
+
| `PlatformService` | SSR environment detection |
|
|
1570
|
+
|
|
1571
|
+
### Classes
|
|
1572
|
+
|
|
1573
|
+
| Class | Description |
|
|
1574
|
+
| -------------------- | -------------------------------------------------- |
|
|
1575
|
+
| `ApiResourceService` | Signal-based CRUD base class (alias: `ApiService`) |
|
|
1576
|
+
| `BaseFormControl` | Abstract base for custom form controls |
|
|
1577
|
+
| `BaseFormPage` | Abstract directive for create/edit pages |
|
|
1578
|
+
| `BaseListPage` | Abstract directive for list pages |
|
|
1579
|
+
|
|
1580
|
+
### Constants
|
|
1581
|
+
|
|
1582
|
+
| Constant | Description |
|
|
1583
|
+
| ------------------- | ----------------------------------------------------- |
|
|
1584
|
+
| `PERMISSIONS` | Aggregated permission codes by module |
|
|
1585
|
+
| `USER_PERMISSIONS` | `{ CREATE, READ, UPDATE, DELETE }` for users |
|
|
1586
|
+
| `ROLE_PERMISSIONS` | `{ CREATE, READ, UPDATE, DELETE }` for roles |
|
|
1587
|
+
| `FILE_PERMISSIONS` | `{ CREATE, READ, UPDATE, DELETE }` for files |
|
|
1588
|
+
| `FILE_TYPE_FILTERS` | Predefined MIME type arrays (IMAGES, DOCUMENTS, etc.) |
|
|
1314
1589
|
|
|
1315
1590
|
### Components
|
|
1316
1591
|
|
|
1317
|
-
| Component
|
|
1318
|
-
|
|
|
1319
|
-
| `IconComponent`
|
|
1320
|
-
| `LazySelectComponent`
|
|
1321
|
-
| `LazyMultiSelectComponent`
|
|
1322
|
-
| `UserSelectComponent`
|
|
1323
|
-
| `UserMultiSelectComponent`
|
|
1324
|
-
| `FileUploaderComponent`
|
|
1325
|
-
| `FileSelectorDialogComponent` | `lib-file-selector-dialog` | File browser dialog
|
|
1592
|
+
| Component | Selector | Description |
|
|
1593
|
+
| ----------------------------- | -------------------------- | ------------------------- |
|
|
1594
|
+
| `IconComponent` | `lib-icon` | Flexible icon renderer |
|
|
1595
|
+
| `LazySelectComponent` | `lib-lazy-select` | Lazy-loading dropdown |
|
|
1596
|
+
| `LazyMultiSelectComponent` | `lib-lazy-multi-select` | Lazy-loading multi-select |
|
|
1597
|
+
| `UserSelectComponent` | `lib-user-select` | Single user selector |
|
|
1598
|
+
| `UserMultiSelectComponent` | `lib-user-multi-select` | Multiple user selector |
|
|
1599
|
+
| `FileUploaderComponent` | `lib-file-uploader` | Drag & drop file upload |
|
|
1600
|
+
| `FileSelectorDialogComponent` | `lib-file-selector-dialog` | File browser dialog |
|
|
1326
1601
|
|
|
1327
1602
|
### Directives
|
|
1328
1603
|
|
|
1329
|
-
| Directive | Selector
|
|
1330
|
-
| --------------------------------- |
|
|
1331
|
-
| `HasPermissionDirective` | `[hasPermission]`
|
|
1604
|
+
| Directive | Selector | Description |
|
|
1605
|
+
| --------------------------------- | ----------------------------- | --------------------------------- |
|
|
1606
|
+
| `HasPermissionDirective` | `[hasPermission]` | Permission-based rendering |
|
|
1332
1607
|
| `EditModeElementChangerDirective` | `[appEditModeElementChanger]` | Toggle edit mode on form controls |
|
|
1333
|
-
| `IsEmptyImageDirective` | `img`
|
|
1334
|
-
| `PreventDefaultDirective` | `[appPreventDefault]`
|
|
1608
|
+
| `IsEmptyImageDirective` | `img` | Image fallback on error/empty |
|
|
1609
|
+
| `PreventDefaultDirective` | `[appPreventDefault]` | Prevent default event behavior |
|
|
1335
1610
|
|
|
1336
1611
|
### Guards
|
|
1337
1612
|
|
|
1338
|
-
| Guard
|
|
1339
|
-
|
|
|
1340
|
-
| `permissionGuard`
|
|
1341
|
-
| `anyPermissionGuard`
|
|
1342
|
-
| `allPermissionsGuard`
|
|
1613
|
+
| Guard | Description |
|
|
1614
|
+
| --------------------- | ------------------------------------- |
|
|
1615
|
+
| `permissionGuard` | Single permission or ILogicNode check |
|
|
1616
|
+
| `anyPermissionGuard` | OR logic (any of listed permissions) |
|
|
1617
|
+
| `allPermissionsGuard` | AND logic (all of listed permissions) |
|
|
1343
1618
|
|
|
1344
1619
|
### Interfaces
|
|
1345
1620
|
|
|
1346
|
-
| Interface
|
|
1347
|
-
|
|
|
1348
|
-
| `IBaseEntity`
|
|
1349
|
-
| `ILoggedUserInfo`
|
|
1350
|
-
| `IFilterData`
|
|
1351
|
-
| `
|
|
1352
|
-
| `
|
|
1353
|
-
| `
|
|
1354
|
-
| `
|
|
1355
|
-
| `
|
|
1356
|
-
| `
|
|
1357
|
-
| `
|
|
1358
|
-
| `
|
|
1359
|
-
| `
|
|
1360
|
-
| `
|
|
1361
|
-
| `
|
|
1362
|
-
| `
|
|
1363
|
-
| `
|
|
1364
|
-
| `
|
|
1365
|
-
| `
|
|
1366
|
-
| `
|
|
1367
|
-
| `
|
|
1368
|
-
| `
|
|
1369
|
-
| `
|
|
1370
|
-
| `
|
|
1371
|
-
| `
|
|
1621
|
+
| Interface | Description |
|
|
1622
|
+
| ----------------------- | ------------------------------------------------------------------------------ |
|
|
1623
|
+
| `IBaseEntity` | Base entity with ID and timestamps |
|
|
1624
|
+
| `ILoggedUserInfo` | Current user info with company ctx |
|
|
1625
|
+
| `IFilterData` | Filter, pagination, sort payload |
|
|
1626
|
+
| `IFilter` | Filter object (supports arrays) |
|
|
1627
|
+
| `IDeleteData` | Delete request payload |
|
|
1628
|
+
| `IDropDown` | Simple label/value pair |
|
|
1629
|
+
| `ISingleResponse<T>` | Single item response |
|
|
1630
|
+
| `IListResponse<T>` | List with pagination |
|
|
1631
|
+
| `IBulkResponse<T>` | Bulk operation response |
|
|
1632
|
+
| `IMessageResponse` | Message-only response |
|
|
1633
|
+
| `IErrorResponse` | Error with validation details |
|
|
1634
|
+
| `ILogicNode` | Permission logic tree (AND/OR nodes) |
|
|
1635
|
+
| `IUserSelectFilter` | User select filter params |
|
|
1636
|
+
| `LoadUsersFn` | User loading function type |
|
|
1637
|
+
| `IFileBasicInfo` | Basic file info for selectors |
|
|
1638
|
+
| `IFileUploadOptions` | Upload options (compression, etc.) |
|
|
1639
|
+
| `IUploadedFile` | Uploaded file response |
|
|
1640
|
+
| `IFileSelectFilter` | File select filter params |
|
|
1641
|
+
| `LoadFilesFn` | File loading function type |
|
|
1642
|
+
| `UploadFileFn` | File upload function type |
|
|
1643
|
+
| `FilesResponseDto` | File URL service response |
|
|
1644
|
+
| `IAuthStateProvider` | Auth state provider interface |
|
|
1645
|
+
| `IUserListProvider` | User list extensions provider |
|
|
1646
|
+
| `IUserListItem` | Base user for list operations |
|
|
1647
|
+
| `IUserListAction` | User list action definition |
|
|
1648
|
+
| `IUserListColumn` | Extra column for user list |
|
|
1649
|
+
| `IUserListFilter` | User list filter parameters |
|
|
1650
|
+
| `IUserBranchPermission` | User permissions per branch |
|
|
1651
|
+
| `ServiceName` | `'auth' \| 'administration' \| 'iam' \| 'storage' \| 'formBuilder' \| 'email'` |
|
|
1372
1652
|
|
|
1373
1653
|
### Injection Tokens
|
|
1374
1654
|
|
|
1375
|
-
| Token
|
|
1376
|
-
|
|
|
1377
|
-
| `USER_PROVIDER`
|
|
1378
|
-
| `COMPANY_API_PROVIDER`
|
|
1379
|
-
| `USER_PERMISSION_PROVIDER`
|
|
1380
|
-
| `
|
|
1381
|
-
| `
|
|
1382
|
-
| `
|
|
1383
|
-
| `USER_LIST_PROVIDER`
|
|
1655
|
+
| Token | Interface | Optional | Description |
|
|
1656
|
+
| ----------------------------- | ---------------------------- | -------- | ------------------------------------- |
|
|
1657
|
+
| `USER_PROVIDER` | `IUserProvider` | No | User list for IAM |
|
|
1658
|
+
| `COMPANY_API_PROVIDER` | `ICompanyApiProvider` | No | Company list for IAM |
|
|
1659
|
+
| `USER_PERMISSION_PROVIDER` | `IUserPermissionProvider` | No | User permission queries |
|
|
1660
|
+
| `AUTH_STATE_PROVIDER` | `IAuthStateProvider` | No | Auth state for feature packages |
|
|
1661
|
+
| `PROFILE_UPLOAD_PROVIDER` | `IProfileUploadProvider` | Yes | Profile picture upload (ng-storage) |
|
|
1662
|
+
| `PROFILE_PERMISSION_PROVIDER` | `IProfilePermissionProvider` | Yes | User permissions for profile (ng-iam) |
|
|
1663
|
+
| `USER_LIST_PROVIDER` | `IUserListProvider` | Yes | User list extensions |
|
|
1384
1664
|
|
|
1385
1665
|
## See Also
|
|
1386
1666
|
|
|
@@ -1391,5 +1671,6 @@ export const appConfig: ApplicationConfig = {
|
|
|
1391
1671
|
|
|
1392
1672
|
---
|
|
1393
1673
|
|
|
1394
|
-
**Last Updated:** 2026-02-
|
|
1674
|
+
**Last Updated:** 2026-02-25
|
|
1675
|
+
**Version:** 3.0.1
|
|
1395
1676
|
**Angular Version:** 21
|