@flusys/ng-shared 3.0.0 → 4.0.0-lts
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 +435 -359
- package/fesm2022/flusys-ng-shared.mjs +1648 -614
- package/fesm2022/flusys-ng-shared.mjs.map +1 -1
- package/package.json +5 -3
- package/types/flusys-ng-shared.d.ts +286 -308
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
## Package Information
|
|
10
10
|
|
|
11
11
|
- **Package:** `@flusys/ng-shared`
|
|
12
|
-
- **Version:**
|
|
12
|
+
- **Version:** 4.0.0-rc
|
|
13
13
|
- **Dependencies:** ng-core
|
|
14
14
|
- **Dependents:** ng-layout, ng-auth, ng-iam, ng-storage, flusysng
|
|
15
15
|
- **Build Command:** `npm run build:ng-shared`
|
|
@@ -38,13 +38,13 @@ interface IBaseEntity {
|
|
|
38
38
|
|
|
39
39
|
**Entity Mixins:**
|
|
40
40
|
|
|
41
|
-
| Interface
|
|
42
|
-
|
|
|
43
|
-
| `ISoftDeletable
|
|
44
|
-
| `ITimestampable
|
|
45
|
-
| `IActivatable`
|
|
46
|
-
| `IOrderable`
|
|
47
|
-
| `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 |
|
|
48
48
|
|
|
49
49
|
#### ILoggedUserInfo
|
|
50
50
|
|
|
@@ -74,7 +74,9 @@ interface IPagination {
|
|
|
74
74
|
currentPage: number;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
interface ISort {
|
|
77
|
+
interface ISort {
|
|
78
|
+
[key: string]: "ASC" | "DESC";
|
|
79
|
+
}
|
|
78
80
|
|
|
79
81
|
// Filter supports primitives and arrays for multi-value filtering
|
|
80
82
|
interface IFilter {
|
|
@@ -98,10 +100,10 @@ interface IFilterData {
|
|
|
98
100
|
Delete request payload matching backend DeleteDto.
|
|
99
101
|
|
|
100
102
|
```typescript
|
|
101
|
-
type DeleteType =
|
|
103
|
+
type DeleteType = "delete" | "restore" | "permanent";
|
|
102
104
|
|
|
103
105
|
interface IDeleteData {
|
|
104
|
-
id: string | string[];
|
|
106
|
+
id: string | string[]; // Single or batch delete
|
|
105
107
|
type: DeleteType;
|
|
106
108
|
}
|
|
107
109
|
```
|
|
@@ -124,7 +126,6 @@ interface IUserSelectFilter {
|
|
|
124
126
|
page: number;
|
|
125
127
|
pageSize: number;
|
|
126
128
|
search: string;
|
|
127
|
-
[key: string]: unknown;
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
type LoadUsersFn = (filter: IUserSelectFilter) => Observable<IListResponse<IUserBasicInfo>>;
|
|
@@ -151,7 +152,7 @@ interface IFileUploadOptions {
|
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
interface IUploadedFile {
|
|
154
|
-
id?: string;
|
|
155
|
+
id?: string; // File manager ID (UUID) - available when registered
|
|
155
156
|
name: string;
|
|
156
157
|
key: string;
|
|
157
158
|
size: number;
|
|
@@ -164,7 +165,6 @@ interface IFileSelectFilter {
|
|
|
164
165
|
search: string;
|
|
165
166
|
contentTypes?: string[];
|
|
166
167
|
folderId?: string;
|
|
167
|
-
[key: string]: unknown;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
type LoadFilesFn = (filter: IFileSelectFilter) => Observable<IListResponse<IFileBasicInfo>>;
|
|
@@ -175,18 +175,18 @@ type GetFileUrlsFn = (fileIds: string[]) => Observable<ISingleResponse<IFileBasi
|
|
|
175
175
|
**File Type Filters (Constants):**
|
|
176
176
|
|
|
177
177
|
```typescript
|
|
178
|
-
import { FILE_TYPE_FILTERS, getAcceptString, isFileTypeAllowed, getFileIconClass, formatFileSize } from
|
|
179
|
-
|
|
180
|
-
FILE_TYPE_FILTERS.IMAGES
|
|
181
|
-
FILE_TYPE_FILTERS.DOCUMENTS
|
|
182
|
-
FILE_TYPE_FILTERS.VIDEOS
|
|
183
|
-
FILE_TYPE_FILTERS.AUDIO
|
|
184
|
-
FILE_TYPE_FILTERS.ALL
|
|
185
|
-
|
|
186
|
-
getAcceptString([
|
|
187
|
-
isFileTypeAllowed(file, [
|
|
188
|
-
getFileIconClass(
|
|
189
|
-
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'
|
|
190
190
|
```
|
|
191
191
|
|
|
192
192
|
### Response Interfaces
|
|
@@ -242,12 +242,12 @@ type ApiResponse<T> = ISingleResponse<T> | IListResponse<T> | IBulkResponse<T> |
|
|
|
242
242
|
|
|
243
243
|
**Metadata types:**
|
|
244
244
|
|
|
245
|
-
| Interface | Fields
|
|
246
|
-
| ------------------ |
|
|
247
|
-
| `IRequestMeta` | `requestId?, timestamp?, responseTime?`
|
|
245
|
+
| Interface | Fields |
|
|
246
|
+
| ------------------ | ----------------------------------------------------- |
|
|
247
|
+
| `IRequestMeta` | `requestId?, timestamp?, responseTime?` |
|
|
248
248
|
| `IPaginationMeta` | `total, page, pageSize, count, hasMore?, totalPages?` |
|
|
249
|
-
| `IBulkMeta` | `count, failed?, total?`
|
|
250
|
-
| `IValidationError` | `field, message, constraint?`
|
|
249
|
+
| `IBulkMeta` | `count, failed?, total?` |
|
|
250
|
+
| `IValidationError` | `field, message, constraint?` |
|
|
251
251
|
|
|
252
252
|
### Auth & Storage Response Types
|
|
253
253
|
|
|
@@ -292,14 +292,14 @@ Discriminated union for building complex permission logic trees.
|
|
|
292
292
|
```typescript
|
|
293
293
|
// Single permission check
|
|
294
294
|
interface IActionNode {
|
|
295
|
-
type:
|
|
295
|
+
type: "action";
|
|
296
296
|
actionId: string;
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
// Group with AND/OR logic
|
|
300
300
|
interface IGroupNode {
|
|
301
|
-
type:
|
|
302
|
-
operator:
|
|
301
|
+
type: "group";
|
|
302
|
+
operator: "AND" | "OR";
|
|
303
303
|
children: ILogicNode[];
|
|
304
304
|
}
|
|
305
305
|
|
|
@@ -374,25 +374,25 @@ Signal-based CRUD service using Angular 21 `resource()` API with **lazy initiali
|
|
|
374
374
|
**ServiceName Type:**
|
|
375
375
|
|
|
376
376
|
```typescript
|
|
377
|
-
type ServiceName =
|
|
377
|
+
type ServiceName = "auth" | "administration" | "iam" | "storage" | "formBuilder" | "email";
|
|
378
378
|
```
|
|
379
379
|
|
|
380
380
|
**Define a service:**
|
|
381
381
|
|
|
382
382
|
```typescript
|
|
383
|
-
import { Injectable } from
|
|
384
|
-
import { HttpClient } from
|
|
385
|
-
import { ApiResourceService } from
|
|
383
|
+
import { Injectable } from "@angular/core";
|
|
384
|
+
import { HttpClient } from "@angular/common/http";
|
|
385
|
+
import { ApiResourceService } from "@flusys/ng-shared";
|
|
386
386
|
|
|
387
|
-
@Injectable({ providedIn:
|
|
387
|
+
@Injectable({ providedIn: "root" })
|
|
388
388
|
export class UserService extends ApiResourceService<UserDto, IUser> {
|
|
389
389
|
constructor(http: HttpClient) {
|
|
390
390
|
// Option 1: Use global apiBaseUrl (default)
|
|
391
|
-
super(
|
|
391
|
+
super("users", http);
|
|
392
392
|
// Base URL: APP_CONFIG.apiBaseUrl + '/users'
|
|
393
393
|
|
|
394
394
|
// Option 2: Use feature-specific service URL (recommended)
|
|
395
|
-
super(
|
|
395
|
+
super("users", http, "administration");
|
|
396
396
|
// Base URL: APP_CONFIG.services.administration.baseUrl + '/users'
|
|
397
397
|
}
|
|
398
398
|
}
|
|
@@ -400,13 +400,14 @@ export class UserService extends ApiResourceService<UserDto, IUser> {
|
|
|
400
400
|
|
|
401
401
|
**Constructor Parameters:**
|
|
402
402
|
|
|
403
|
-
| Parameter
|
|
404
|
-
|
|
405
|
-
| `moduleApiName` | `string`
|
|
406
|
-
| `http`
|
|
407
|
-
| `serviceName`
|
|
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
408
|
|
|
409
409
|
**Service URL Resolution:**
|
|
410
|
+
|
|
410
411
|
- If `serviceName` is provided, uses `getServiceUrl(config, serviceName)` to resolve the base URL
|
|
411
412
|
- Falls back to `APP_CONFIG.apiBaseUrl` if service not found or `serviceName` not provided
|
|
412
413
|
|
|
@@ -416,7 +417,7 @@ The list resource is **lazy-initialized** to avoid unnecessary HTTP requests on
|
|
|
416
417
|
|
|
417
418
|
```typescript
|
|
418
419
|
// Resource is NOT created until one of these is called:
|
|
419
|
-
userService.fetchList(
|
|
420
|
+
userService.fetchList("", { pagination: { currentPage: 0, pageSize: 10 } });
|
|
420
421
|
// OR
|
|
421
422
|
userService.initListResource(); // Explicit initialization
|
|
422
423
|
```
|
|
@@ -439,35 +440,35 @@ initListResource(): void {
|
|
|
439
440
|
|
|
440
441
|
**Endpoint mapping:**
|
|
441
442
|
|
|
442
|
-
| Method
|
|
443
|
-
|
|
|
444
|
-
| `insert(dto)`
|
|
445
|
-
| `insertMany(dtos)`
|
|
446
|
-
| `findById(id, select?)`
|
|
447
|
-
| `getAll(search, filter)` | POST | `/{resource}/get-all?q=`
|
|
448
|
-
| `update(dto)`
|
|
449
|
-
| `updateMany(dtos)`
|
|
450
|
-
| `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` |
|
|
451
452
|
|
|
452
453
|
All methods above return `Observable`. Async (Promise) variants are also available: `insertAsync`, `insertManyAsync`, `findByIdAsync`, `updateAsync`, `updateManyAsync`, `deleteAsync`.
|
|
453
454
|
|
|
454
455
|
**Reactive signals:**
|
|
455
456
|
|
|
456
|
-
| Signal | Type
|
|
457
|
-
| ------------ |
|
|
458
|
-
| `isLoading` | `Signal<boolean>`
|
|
459
|
-
| `data` | `Signal<T[]>`
|
|
460
|
-
| `total` | `Signal<number>`
|
|
461
|
-
| `pageInfo` | `Signal<IPaginationMeta>`
|
|
462
|
-
| `hasMore` | `Signal<boolean>`
|
|
463
|
-
| `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 |
|
|
464
465
|
| `filterData` | `WritableSignal<IFilterData>` | Filter/pagination state |
|
|
465
466
|
|
|
466
467
|
**List management:**
|
|
467
468
|
|
|
468
469
|
```typescript
|
|
469
470
|
// Trigger data fetch (updates searchTerm and filterData signals, resource auto-reloads)
|
|
470
|
-
userService.fetchList(
|
|
471
|
+
userService.fetchList("search term", { pagination: { currentPage: 0, pageSize: 10 } });
|
|
471
472
|
|
|
472
473
|
// Pagination helpers
|
|
473
474
|
userService.setPagination({ currentPage: 1, pageSize: 20 });
|
|
@@ -530,14 +531,14 @@ export class ProductComponent {
|
|
|
530
531
|
|
|
531
532
|
**Methods:**
|
|
532
533
|
|
|
533
|
-
| Method
|
|
534
|
-
|
|
|
535
|
-
| `getFileUrl(fileId)`
|
|
536
|
-
| `fileUrlSignal(fileId)`
|
|
537
|
-
| `fetchFileUrls(fileIds[])`
|
|
538
|
-
| `fetchSingleFileUrl(fileId)`
|
|
539
|
-
| `clearCache()`
|
|
540
|
-
| `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 |
|
|
541
542
|
|
|
542
543
|
### PermissionValidatorService
|
|
543
544
|
|
|
@@ -569,18 +570,18 @@ export class MyComponent {
|
|
|
569
570
|
|
|
570
571
|
**Methods:**
|
|
571
572
|
|
|
572
|
-
| Method
|
|
573
|
-
|
|
|
574
|
-
| `setPermissions(codes[])`
|
|
575
|
-
| `clearPermissions()`
|
|
576
|
-
| `hasPermission(code)`
|
|
577
|
-
| `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 |
|
|
578
579
|
|
|
579
580
|
**Signals:**
|
|
580
581
|
|
|
581
|
-
| Signal | Type | Description
|
|
582
|
-
| ------------- | ------------------ |
|
|
583
|
-
| `permissions` | `Signal<string[]>` | Readonly current permissions
|
|
582
|
+
| Signal | Type | Description |
|
|
583
|
+
| ------------- | ------------------ | --------------------------------- |
|
|
584
|
+
| `permissions` | `Signal<string[]>` | Readonly current permissions |
|
|
584
585
|
| `isLoaded` | `Signal<boolean>` | Whether permissions have been set |
|
|
585
586
|
|
|
586
587
|
**Wildcard Support:**
|
|
@@ -589,13 +590,13 @@ The `hasPermission()` method uses the permission evaluator utility which support
|
|
|
589
590
|
|
|
590
591
|
```typescript
|
|
591
592
|
// Exact match
|
|
592
|
-
hasPermission(
|
|
593
|
+
hasPermission("user.read"); // true if permissions include 'user.read'
|
|
593
594
|
|
|
594
595
|
// Global wildcard
|
|
595
|
-
hasPermission(
|
|
596
|
+
hasPermission("user.read"); // true if permissions include '*'
|
|
596
597
|
|
|
597
598
|
// Module wildcard
|
|
598
|
-
hasPermission(
|
|
599
|
+
hasPermission("user.read"); // true if permissions include 'user.*'
|
|
599
600
|
```
|
|
600
601
|
|
|
601
602
|
### CookieService
|
|
@@ -603,7 +604,7 @@ hasPermission('user.read') // true if permissions include 'user.*'
|
|
|
603
604
|
SSR-aware cookie reading service.
|
|
604
605
|
|
|
605
606
|
```typescript
|
|
606
|
-
import { CookieService } from
|
|
607
|
+
import { CookieService } from "@flusys/ng-shared";
|
|
607
608
|
|
|
608
609
|
const cookies = inject(CookieService).get(); // Returns document.cookie (browser) or request header cookie (server)
|
|
609
610
|
```
|
|
@@ -613,7 +614,7 @@ const cookies = inject(CookieService).get(); // Returns document.cookie (browser
|
|
|
613
614
|
SSR environment detection service.
|
|
614
615
|
|
|
615
616
|
```typescript
|
|
616
|
-
import { PlatformService } from
|
|
617
|
+
import { PlatformService } from "@flusys/ng-shared";
|
|
617
618
|
|
|
618
619
|
const platform = inject(PlatformService);
|
|
619
620
|
if (!platform.isServer) {
|
|
@@ -654,21 +655,7 @@ Single-select dropdown with lazy loading, search, and scroll pagination.
|
|
|
654
655
|
```typescript
|
|
655
656
|
@Component({
|
|
656
657
|
imports: [LazySelectComponent],
|
|
657
|
-
template: `
|
|
658
|
-
<lib-lazy-select
|
|
659
|
-
[(value)]="selectedId"
|
|
660
|
-
[selectDataList]="items()"
|
|
661
|
-
[optionLabel]="'label'"
|
|
662
|
-
[optionValue]="'value'"
|
|
663
|
-
[isEditMode]="true"
|
|
664
|
-
[isLoading]="loading()"
|
|
665
|
-
[total]="total()"
|
|
666
|
-
[pagination]="pagination()"
|
|
667
|
-
[placeHolder]="'Select item'"
|
|
668
|
-
(onSearch)="handleSearch($event)"
|
|
669
|
-
(onPagination)="handlePagination($event)"
|
|
670
|
-
/>
|
|
671
|
-
`,
|
|
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)" /> `,
|
|
672
659
|
})
|
|
673
660
|
export class MyComponent {
|
|
674
661
|
readonly selectedId = signal<string | null>(null);
|
|
@@ -681,16 +668,16 @@ export class MyComponent {
|
|
|
681
668
|
|
|
682
669
|
**Inputs:**
|
|
683
670
|
|
|
684
|
-
| Input | Type
|
|
685
|
-
| ---------------- |
|
|
686
|
-
| `selectDataList` | `Array<IDropDown>`
|
|
687
|
-
| `optionLabel` | `string`
|
|
688
|
-
| `optionValue` | `string`
|
|
689
|
-
| `isEditMode` | `boolean`
|
|
690
|
-
| `isLoading` | `boolean`
|
|
691
|
-
| `total` | `number \| undefined` | Total items for pagination
|
|
692
|
-
| `pagination` | `IPagination`
|
|
693
|
-
| `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'`) |
|
|
694
681
|
|
|
695
682
|
**Model:** `value` - Two-way bound selected value (`string | null`)
|
|
696
683
|
|
|
@@ -707,19 +694,7 @@ Multi-select dropdown with lazy loading, search, select-all, and scroll paginati
|
|
|
707
694
|
```typescript
|
|
708
695
|
@Component({
|
|
709
696
|
imports: [LazyMultiSelectComponent],
|
|
710
|
-
template: `
|
|
711
|
-
<lib-lazy-multi-select
|
|
712
|
-
[(value)]="selectedIds"
|
|
713
|
-
[selectDataList]="items()"
|
|
714
|
-
[isEditMode]="true"
|
|
715
|
-
[isLoading]="loading()"
|
|
716
|
-
[total]="total()"
|
|
717
|
-
[pagination]="pagination()"
|
|
718
|
-
[placeHolder]="'Select items'"
|
|
719
|
-
(onSearch)="handleSearch($event)"
|
|
720
|
-
(onPagination)="handlePagination($event)"
|
|
721
|
-
/>
|
|
722
|
-
`,
|
|
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)" /> `,
|
|
723
698
|
})
|
|
724
699
|
export class MyComponent {
|
|
725
700
|
readonly selectedIds = signal<string[] | null>(null);
|
|
@@ -744,17 +719,10 @@ Single user selection with lazy loading. Uses `USER_PROVIDER` internally or acce
|
|
|
744
719
|
imports: [UserSelectComponent],
|
|
745
720
|
template: `
|
|
746
721
|
<!-- Simple usage - uses USER_PROVIDER internally -->
|
|
747
|
-
<lib-user-select
|
|
748
|
-
[(value)]="selectedUserId"
|
|
749
|
-
[isEditMode]="true"
|
|
750
|
-
/>
|
|
722
|
+
<lib-user-select [(value)]="selectedUserId" [isEditMode]="true" />
|
|
751
723
|
|
|
752
724
|
<!-- With custom loadUsers function -->
|
|
753
|
-
<lib-user-select
|
|
754
|
-
[(value)]="selectedUserId"
|
|
755
|
-
[isEditMode]="true"
|
|
756
|
-
[loadUsers]="customLoadUsers"
|
|
757
|
-
/>
|
|
725
|
+
<lib-user-select [(value)]="selectedUserId" [isEditMode]="true" [loadUsers]="customLoadUsers" />
|
|
758
726
|
`,
|
|
759
727
|
})
|
|
760
728
|
export class MyComponent {
|
|
@@ -764,14 +732,14 @@ export class MyComponent {
|
|
|
764
732
|
|
|
765
733
|
**Inputs:**
|
|
766
734
|
|
|
767
|
-
| Input
|
|
768
|
-
|
|
|
769
|
-
| `isEditMode`
|
|
770
|
-
| `placeHolder`
|
|
771
|
-
| `filterActive`
|
|
772
|
-
| `additionalFilters` | `Record<string, unknown>` | `{}`
|
|
773
|
-
| `pageSize`
|
|
774
|
-
| `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 |
|
|
775
743
|
|
|
776
744
|
**Model:** `value` - Two-way bound selected user ID (`string | null`)
|
|
777
745
|
|
|
@@ -787,12 +755,7 @@ Multiple user selection with lazy loading. Uses `USER_PROVIDER` internally or ac
|
|
|
787
755
|
```typescript
|
|
788
756
|
@Component({
|
|
789
757
|
imports: [UserMultiSelectComponent],
|
|
790
|
-
template: `
|
|
791
|
-
<lib-user-multi-select
|
|
792
|
-
[(value)]="selectedUserIds"
|
|
793
|
-
[isEditMode]="true"
|
|
794
|
-
/>
|
|
795
|
-
`,
|
|
758
|
+
template: ` <lib-user-multi-select [(value)]="selectedUserIds" [isEditMode]="true" /> `,
|
|
796
759
|
})
|
|
797
760
|
export class MyComponent {
|
|
798
761
|
readonly selectedUserIds = signal<string[] | null>(null);
|
|
@@ -816,44 +779,32 @@ Drag & drop file upload with type filtering. Pass your own `uploadFile` function
|
|
|
816
779
|
imports: [FileUploaderComponent],
|
|
817
780
|
template: `
|
|
818
781
|
<!-- Single image upload -->
|
|
819
|
-
<lib-file-uploader
|
|
820
|
-
[uploadFile]="uploadFile"
|
|
821
|
-
[acceptTypes]="['image/*']"
|
|
822
|
-
[multiple]="false"
|
|
823
|
-
(fileUploaded)="onFileUploaded($event)"
|
|
824
|
-
/>
|
|
782
|
+
<lib-file-uploader [uploadFile]="uploadFile" [acceptTypes]="['image/*']" [multiple]="false" (fileUploaded)="onFileUploaded($event)" />
|
|
825
783
|
|
|
826
784
|
<!-- Multiple document upload -->
|
|
827
|
-
<lib-file-uploader
|
|
828
|
-
[uploadFile]="uploadFile"
|
|
829
|
-
[acceptTypes]="FILE_TYPE_FILTERS.DOCUMENTS"
|
|
830
|
-
[multiple]="true"
|
|
831
|
-
[maxFiles]="5"
|
|
832
|
-
(filesUploaded)="onFilesUploaded($event)"
|
|
833
|
-
/>
|
|
785
|
+
<lib-file-uploader [uploadFile]="uploadFile" [acceptTypes]="FILE_TYPE_FILTERS.DOCUMENTS" [multiple]="true" [maxFiles]="5" (filesUploaded)="onFilesUploaded($event)" />
|
|
834
786
|
`,
|
|
835
787
|
})
|
|
836
788
|
export class MyComponent {
|
|
837
789
|
readonly uploadService = inject(UploadService);
|
|
838
790
|
|
|
839
|
-
readonly uploadFile: UploadFileFn = (file, options) =>
|
|
840
|
-
this.uploadService.uploadSingleFile(file, options);
|
|
791
|
+
readonly uploadFile: UploadFileFn = (file, options) => this.uploadService.uploadSingleFile(file, options);
|
|
841
792
|
}
|
|
842
793
|
```
|
|
843
794
|
|
|
844
795
|
**Inputs:**
|
|
845
796
|
|
|
846
|
-
| Input
|
|
847
|
-
|
|
|
848
|
-
| `uploadFile`
|
|
849
|
-
| `acceptTypes`
|
|
850
|
-
| `multiple`
|
|
851
|
-
| `maxFiles`
|
|
852
|
-
| `maxSizeMb`
|
|
853
|
-
| `uploadOptions` | `IFileUploadOptions` | `{}`
|
|
854
|
-
| `disabled`
|
|
855
|
-
| `showPreview`
|
|
856
|
-
| `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 |
|
|
857
808
|
|
|
858
809
|
**Outputs:** `fileUploaded` (IUploadedFile), `filesUploaded` (IUploadedFile[]), `onError` (Error), `fileSelected` (File[])
|
|
859
810
|
|
|
@@ -866,15 +817,7 @@ Dialog to browse and select existing files with filtering.
|
|
|
866
817
|
```typescript
|
|
867
818
|
@Component({
|
|
868
819
|
imports: [FileSelectorDialogComponent],
|
|
869
|
-
template: `
|
|
870
|
-
<lib-file-selector-dialog
|
|
871
|
-
[(visible)]="showFileSelector"
|
|
872
|
-
[loadFiles]="loadFiles"
|
|
873
|
-
[acceptTypes]="['image/*']"
|
|
874
|
-
[multiple]="false"
|
|
875
|
-
(fileSelected)="onFileSelected($event)"
|
|
876
|
-
/>
|
|
877
|
-
`,
|
|
820
|
+
template: ` <lib-file-selector-dialog [(visible)]="showFileSelector" [loadFiles]="loadFiles" [acceptTypes]="['image/*']" [multiple]="false" (fileSelected)="onFileSelected($event)" /> `,
|
|
878
821
|
})
|
|
879
822
|
export class MyComponent {
|
|
880
823
|
readonly showFileSelector = signal(false);
|
|
@@ -889,14 +832,14 @@ export class MyComponent {
|
|
|
889
832
|
|
|
890
833
|
**Inputs:**
|
|
891
834
|
|
|
892
|
-
| Input
|
|
893
|
-
|
|
|
894
|
-
| `loadFiles`
|
|
895
|
-
| `header`
|
|
896
|
-
| `acceptTypes`
|
|
897
|
-
| `multiple`
|
|
898
|
-
| `maxSelection` | `number`
|
|
899
|
-
| `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 |
|
|
900
843
|
|
|
901
844
|
**Model:** `visible` - Two-way bound dialog visibility (`boolean`)
|
|
902
845
|
|
|
@@ -914,7 +857,7 @@ Structural directive for permission-based rendering. Fail-closed: hides content
|
|
|
914
857
|
- **Input:** `hasPermission` - `string | ILogicNode | null`
|
|
915
858
|
|
|
916
859
|
```typescript
|
|
917
|
-
import { HasPermissionDirective, ILogicNode } from
|
|
860
|
+
import { HasPermissionDirective, ILogicNode } from "@flusys/ng-shared";
|
|
918
861
|
|
|
919
862
|
@Component({
|
|
920
863
|
imports: [HasPermissionDirective],
|
|
@@ -928,11 +871,11 @@ import { HasPermissionDirective, ILogicNode } from '@flusys/ng-shared';
|
|
|
928
871
|
})
|
|
929
872
|
export class MyComponent {
|
|
930
873
|
readonly editLogic: ILogicNode = {
|
|
931
|
-
type:
|
|
932
|
-
operator:
|
|
874
|
+
type: "group",
|
|
875
|
+
operator: "AND",
|
|
933
876
|
children: [
|
|
934
|
-
{ type:
|
|
935
|
-
{ type:
|
|
877
|
+
{ type: "action", actionId: "user.view" },
|
|
878
|
+
{ type: "action", actionId: "user.update" },
|
|
936
879
|
],
|
|
937
880
|
};
|
|
938
881
|
}
|
|
@@ -946,8 +889,7 @@ Toggles readonly/disabled state for form controls based on edit mode. Supports `
|
|
|
946
889
|
- **Input:** `isEditMode` (required boolean)
|
|
947
890
|
|
|
948
891
|
```html
|
|
949
|
-
<input [appEditModeElementChanger] [isEditMode]="isEditing()" />
|
|
950
|
-
<p-select [appEditModeElementChanger] [isEditMode]="isEditing()" />
|
|
892
|
+
<input [appEditModeElementChanger] [isEditMode]="isEditing()" /> <p-select [appEditModeElementChanger] [isEditMode]="isEditing()" />
|
|
951
893
|
```
|
|
952
894
|
|
|
953
895
|
### IsEmptyImageDirective
|
|
@@ -972,8 +914,7 @@ Prevents default browser behavior on specified events and emits the event.
|
|
|
972
914
|
- **Output:** `action` - Emits the prevented event
|
|
973
915
|
|
|
974
916
|
```html
|
|
975
|
-
<a href="#" appPreventDefault (action)="handleClick($event)">Click me</a>
|
|
976
|
-
<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)" />
|
|
977
918
|
```
|
|
978
919
|
|
|
979
920
|
---
|
|
@@ -987,24 +928,29 @@ Route-level guards for permission-based access control. All guards deny access w
|
|
|
987
928
|
Single permission or complex logic check.
|
|
988
929
|
|
|
989
930
|
```typescript
|
|
990
|
-
import { permissionGuard } from
|
|
931
|
+
import { permissionGuard } from "@flusys/ng-shared";
|
|
991
932
|
|
|
992
933
|
const routes: Routes = [
|
|
993
934
|
// Simple permission
|
|
994
|
-
{ path:
|
|
935
|
+
{ path: "users", canActivate: [permissionGuard("user.view")] },
|
|
995
936
|
|
|
996
937
|
// Complex logic (ILogicNode)
|
|
997
|
-
{
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
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
|
+
}),
|
|
1003
949
|
],
|
|
1004
|
-
}
|
|
950
|
+
},
|
|
1005
951
|
|
|
1006
952
|
// Custom redirect on deny
|
|
1007
|
-
{ path:
|
|
953
|
+
{ path: "settings", canActivate: [permissionGuard("settings.view", "/access-denied")] },
|
|
1008
954
|
];
|
|
1009
955
|
```
|
|
1010
956
|
|
|
@@ -1033,34 +979,34 @@ AND logic - allows access only if user has ALL specified permissions.
|
|
|
1033
979
|
Pure functions for permission logic evaluation. Used internally by `HasPermissionDirective` and guards. **Supports wildcard permissions.**
|
|
1034
980
|
|
|
1035
981
|
```typescript
|
|
1036
|
-
import { evaluatePermission, evaluateLogicNode, hasAnyPermission, hasAllPermissions, hasPermission } from
|
|
982
|
+
import { evaluatePermission, evaluateLogicNode, hasAnyPermission, hasAllPermissions, hasPermission } from "@flusys/ng-shared";
|
|
1037
983
|
|
|
1038
|
-
const userPermissions = [
|
|
984
|
+
const userPermissions = ["user.view", "user.create", "admin.*"];
|
|
1039
985
|
|
|
1040
986
|
// Low-level hasPermission check with wildcard support
|
|
1041
|
-
hasPermission(
|
|
1042
|
-
hasPermission(
|
|
1043
|
-
hasPermission(
|
|
987
|
+
hasPermission("user.view", userPermissions); // true (exact match)
|
|
988
|
+
hasPermission("admin.manage", userPermissions); // true (matches 'admin.*')
|
|
989
|
+
hasPermission("settings.view", ["*"]); // true (global wildcard)
|
|
1044
990
|
|
|
1045
991
|
// Evaluate string or ILogicNode
|
|
1046
|
-
evaluatePermission(
|
|
1047
|
-
evaluatePermission(null, userPermissions);
|
|
992
|
+
evaluatePermission("user.view", userPermissions); // true
|
|
993
|
+
evaluatePermission(null, userPermissions); // false
|
|
1048
994
|
|
|
1049
995
|
// Evaluate ILogicNode tree recursively
|
|
1050
996
|
evaluateLogicNode(logicNode, userPermissions);
|
|
1051
997
|
|
|
1052
998
|
// Simple OR/AND checks (also support wildcards)
|
|
1053
|
-
hasAnyPermission([
|
|
1054
|
-
hasAllPermissions([
|
|
999
|
+
hasAnyPermission(["user.view", "user.delete"], userPermissions); // true (has user.view)
|
|
1000
|
+
hasAllPermissions(["user.view", "user.delete"], userPermissions); // false (missing user.delete)
|
|
1055
1001
|
```
|
|
1056
1002
|
|
|
1057
1003
|
**Wildcard Rules:**
|
|
1058
1004
|
|
|
1059
|
-
| Pattern
|
|
1060
|
-
|
|
1061
|
-
| `*`
|
|
1062
|
-
| `module.*`
|
|
1063
|
-
| `user.read` | Exact match only
|
|
1005
|
+
| Pattern | Matches |
|
|
1006
|
+
| ----------- | --------------------------------------- |
|
|
1007
|
+
| `*` | All permissions (global wildcard) |
|
|
1008
|
+
| `module.*` | All permissions starting with `module.` |
|
|
1009
|
+
| `user.read` | Exact match only |
|
|
1064
1010
|
|
|
1065
1011
|
**Implementation Details:**
|
|
1066
1012
|
|
|
@@ -1072,10 +1018,10 @@ export function hasPermission(requiredPermission: string, userPermissions: strin
|
|
|
1072
1018
|
// Wildcard matching
|
|
1073
1019
|
for (const permission of userPermissions) {
|
|
1074
1020
|
// Global wildcard
|
|
1075
|
-
if (permission ===
|
|
1021
|
+
if (permission === "*") return true;
|
|
1076
1022
|
|
|
1077
1023
|
// Module wildcard (e.g., 'user.*' matches 'user.read')
|
|
1078
|
-
if (permission.endsWith(
|
|
1024
|
+
if (permission.endsWith(".*")) {
|
|
1079
1025
|
const prefix = permission.slice(0, -1); // 'user.'
|
|
1080
1026
|
if (requiredPermission.startsWith(prefix)) return true;
|
|
1081
1027
|
}
|
|
@@ -1119,10 +1065,10 @@ onScroll(event: Event): void {
|
|
|
1119
1065
|
Abstract base class for custom form controls. Implements both `ControlValueAccessor` (reactive forms) and `FormValueControl` (signal forms).
|
|
1120
1066
|
|
|
1121
1067
|
```typescript
|
|
1122
|
-
import { BaseFormControl, provideValueAccessor } from
|
|
1068
|
+
import { BaseFormControl, provideValueAccessor } from "@flusys/ng-shared";
|
|
1123
1069
|
|
|
1124
1070
|
@Component({
|
|
1125
|
-
selector:
|
|
1071
|
+
selector: "my-select",
|
|
1126
1072
|
providers: [provideValueAccessor(MySelectComponent)],
|
|
1127
1073
|
})
|
|
1128
1074
|
export class MySelectComponent extends BaseFormControl<string | null> {
|
|
@@ -1226,7 +1172,7 @@ export class UserListComponent extends BaseListPage<IUser> {
|
|
|
1226
1172
|
Re-exports common Angular modules for convenience.
|
|
1227
1173
|
|
|
1228
1174
|
```typescript
|
|
1229
|
-
import { AngularModule } from
|
|
1175
|
+
import { AngularModule } from "@flusys/ng-shared";
|
|
1230
1176
|
// Includes: CommonModule, FormsModule, ReactiveFormsModule, RouterLink, RouterOutlet,
|
|
1231
1177
|
// RouterLinkActive, NgOptimizedImage, NgComponentOutlet, + directives (IsEmptyImageDirective, PreventDefaultDirective)
|
|
1232
1178
|
// Providers: DatePipe
|
|
@@ -1237,7 +1183,7 @@ import { AngularModule } from '@flusys/ng-shared';
|
|
|
1237
1183
|
Re-exports PrimeNG component modules for convenience.
|
|
1238
1184
|
|
|
1239
1185
|
```typescript
|
|
1240
|
-
import { PrimeModule } from
|
|
1186
|
+
import { PrimeModule } from "@flusys/ng-shared";
|
|
1241
1187
|
// Includes 30 modules:
|
|
1242
1188
|
// - Layout: AccordionModule, CardModule, DividerModule, PanelModule, SplitterModule, TabsModule, ToolbarModule
|
|
1243
1189
|
// - Form: AutoCompleteModule, CheckboxModule, DatePickerModule, InputTextModule, InputTextareaModule,
|
|
@@ -1273,13 +1219,14 @@ ng-iam/ng-storage (consume interfaces via DI)
|
|
|
1273
1219
|
User list access for IAM user selection.
|
|
1274
1220
|
|
|
1275
1221
|
```typescript
|
|
1276
|
-
interface IUserBasicInfo {
|
|
1222
|
+
interface IUserBasicInfo {
|
|
1223
|
+
id: string;
|
|
1224
|
+
name: string;
|
|
1225
|
+
email: string;
|
|
1226
|
+
}
|
|
1277
1227
|
|
|
1278
1228
|
interface IUserProvider {
|
|
1279
|
-
getUsers(filter?: {
|
|
1280
|
-
page?: number; pageSize?: number; search?: string;
|
|
1281
|
-
companyId?: string; branchId?: string;
|
|
1282
|
-
}): Observable<IListResponse<IUserBasicInfo>>;
|
|
1229
|
+
getUsers(filter?: { page?: number; pageSize?: number; search?: string; companyId?: string; branchId?: string }): Observable<IListResponse<IUserBasicInfo>>;
|
|
1283
1230
|
}
|
|
1284
1231
|
```
|
|
1285
1232
|
|
|
@@ -1290,12 +1237,14 @@ interface IUserProvider {
|
|
|
1290
1237
|
Company list access for IAM company selection.
|
|
1291
1238
|
|
|
1292
1239
|
```typescript
|
|
1293
|
-
interface ICompanyBasicInfo {
|
|
1240
|
+
interface ICompanyBasicInfo {
|
|
1241
|
+
id: string;
|
|
1242
|
+
name: string;
|
|
1243
|
+
slug?: string;
|
|
1244
|
+
}
|
|
1294
1245
|
|
|
1295
1246
|
interface ICompanyApiProvider {
|
|
1296
|
-
getCompanies(filter?: {
|
|
1297
|
-
page?: number; pageSize?: number; search?: string;
|
|
1298
|
-
}): Observable<IListResponse<ICompanyBasicInfo>>;
|
|
1247
|
+
getCompanies(filter?: { page?: number; pageSize?: number; search?: string }): Observable<IListResponse<ICompanyBasicInfo>>;
|
|
1299
1248
|
}
|
|
1300
1249
|
```
|
|
1301
1250
|
|
|
@@ -1319,31 +1268,18 @@ interface IUserPermissionProvider {
|
|
|
1319
1268
|
|
|
1320
1269
|
**Token Error Message:** `'USER_PERMISSION_PROVIDER not configured. Please provide an implementation in app.config.ts'`
|
|
1321
1270
|
|
|
1322
|
-
####
|
|
1271
|
+
#### IFileProvider / `FILE_PROVIDER`
|
|
1323
1272
|
|
|
1324
|
-
|
|
1273
|
+
File operations provider for file selection and upload. Implemented by ng-storage. **Optional token** - use with `inject(..., { optional: true })`.
|
|
1325
1274
|
|
|
1326
1275
|
```typescript
|
|
1327
|
-
interface
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
interface IProfileUploadOptions {
|
|
1336
|
-
folderPath?: string;
|
|
1337
|
-
compress?: boolean;
|
|
1338
|
-
maxWidth?: number;
|
|
1339
|
-
maxHeight?: number;
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
interface IProfileUploadProvider {
|
|
1343
|
-
uploadProfilePicture(
|
|
1344
|
-
file: File,
|
|
1345
|
-
options?: IProfileUploadOptions
|
|
1346
|
-
): Observable<ISingleResponse<IProfileUploadResult>>;
|
|
1276
|
+
interface IFileProvider {
|
|
1277
|
+
loadFiles(filter: IFileSelectFilter): Observable<IListResponse<IFileBasicInfo>>;
|
|
1278
|
+
uploadFile(file: File, options?: IFileUploadOptions): Observable<ISingleResponse<IUploadedFile>>;
|
|
1279
|
+
uploadMultipleFiles?(files: File[], options?: IFileUploadOptions): Observable<ISingleResponse<IUploadedFile[]>>;
|
|
1280
|
+
getFileUrls?(fileIds: string[]): Observable<ISingleResponse<IFileBasicInfo[]>>;
|
|
1281
|
+
loadFolders?(filter: ISelectFilter): Observable<IListResponse<IFolderBasicInfo>>;
|
|
1282
|
+
loadStorageConfigs?(filter: ISelectFilter): Observable<IListResponse<IStorageConfigBasicInfo>>;
|
|
1347
1283
|
}
|
|
1348
1284
|
```
|
|
1349
1285
|
|
|
@@ -1417,14 +1353,13 @@ interface IUserListItem {
|
|
|
1417
1353
|
email: string;
|
|
1418
1354
|
phone?: string;
|
|
1419
1355
|
isActive?: boolean;
|
|
1420
|
-
[key: string]: unknown;
|
|
1421
1356
|
}
|
|
1422
1357
|
|
|
1423
1358
|
interface IUserListAction<T = IUserListItem> {
|
|
1424
1359
|
id: string;
|
|
1425
1360
|
label: string;
|
|
1426
1361
|
icon?: string;
|
|
1427
|
-
severity?:
|
|
1362
|
+
severity?: "primary" | "secondary" | "success" | "info" | "warn" | "danger";
|
|
1428
1363
|
permission?: string;
|
|
1429
1364
|
tooltip?: string;
|
|
1430
1365
|
disabled?: boolean | ((user: T) => boolean);
|
|
@@ -1436,7 +1371,7 @@ interface IUserListColumn {
|
|
|
1436
1371
|
header: string;
|
|
1437
1372
|
width?: string;
|
|
1438
1373
|
sortable?: boolean;
|
|
1439
|
-
templateType?:
|
|
1374
|
+
templateType?: "text" | "badge" | "date" | "boolean" | "custom";
|
|
1440
1375
|
}
|
|
1441
1376
|
|
|
1442
1377
|
interface IUserListFilter {
|
|
@@ -1446,7 +1381,6 @@ interface IUserListFilter {
|
|
|
1446
1381
|
isActive?: boolean;
|
|
1447
1382
|
companyId?: string;
|
|
1448
1383
|
branchId?: string;
|
|
1449
|
-
[key: string]: unknown;
|
|
1450
1384
|
}
|
|
1451
1385
|
|
|
1452
1386
|
interface IUserListProvider<T extends IUserListItem = IUserListItem> {
|
|
@@ -1463,17 +1397,13 @@ interface IUserListProvider<T extends IUserListItem = IUserListItem> {
|
|
|
1463
1397
|
|
|
1464
1398
|
```typescript
|
|
1465
1399
|
// In app.config.ts
|
|
1466
|
-
providers: [
|
|
1467
|
-
{ provide: USER_LIST_PROVIDER, useClass: MyUserListProvider },
|
|
1468
|
-
]
|
|
1400
|
+
providers: [{ provide: USER_LIST_PROVIDER, useClass: MyUserListProvider }];
|
|
1469
1401
|
|
|
1470
1402
|
// Implementation
|
|
1471
|
-
@Injectable({ providedIn:
|
|
1403
|
+
@Injectable({ providedIn: "root" })
|
|
1472
1404
|
export class MyUserListProvider implements IUserListProvider {
|
|
1473
1405
|
getExtraRowActions() {
|
|
1474
|
-
return [
|
|
1475
|
-
{ id: 'assign-role', label: 'Assign Role', icon: 'pi pi-users' },
|
|
1476
|
-
];
|
|
1406
|
+
return [{ id: "assign-role", label: "Assign Role", icon: "pi pi-users" }];
|
|
1477
1407
|
}
|
|
1478
1408
|
}
|
|
1479
1409
|
```
|
|
@@ -1481,13 +1411,13 @@ export class MyUserListProvider implements IUserListProvider {
|
|
|
1481
1411
|
### Usage in Consuming Packages
|
|
1482
1412
|
|
|
1483
1413
|
```typescript
|
|
1484
|
-
import { USER_PROVIDER } from
|
|
1414
|
+
import { USER_PROVIDER } from "@flusys/ng-shared";
|
|
1485
1415
|
|
|
1486
1416
|
export class UserSelectorComponent {
|
|
1487
1417
|
private readonly userProvider = inject(USER_PROVIDER);
|
|
1488
1418
|
|
|
1489
1419
|
loadUsers() {
|
|
1490
|
-
this.userProvider.getUsers({ page: 0, pageSize: 50 }).subscribe(response => {
|
|
1420
|
+
this.userProvider.getUsers({ page: 0, pageSize: 50 }).subscribe((response) => {
|
|
1491
1421
|
this.users.set(response.data ?? []);
|
|
1492
1422
|
});
|
|
1493
1423
|
}
|
|
@@ -1498,7 +1428,7 @@ export class UserSelectorComponent {
|
|
|
1498
1428
|
|
|
1499
1429
|
```typescript
|
|
1500
1430
|
// app.config.ts
|
|
1501
|
-
import { provideAuthProviders } from
|
|
1431
|
+
import { provideAuthProviders } from "@flusys/ng-auth";
|
|
1502
1432
|
|
|
1503
1433
|
export const appConfig: ApplicationConfig = {
|
|
1504
1434
|
providers: [
|
|
@@ -1598,12 +1528,10 @@ providers: [
|
|
|
1598
1528
|
{ provide: COMPANY_API_PROVIDER, useClass: AuthCompanyApiProvider },
|
|
1599
1529
|
{ provide: USER_PERMISSION_PROVIDER, useClass: AuthUserPermissionProvider },
|
|
1600
1530
|
{ provide: AUTH_STATE_PROVIDER, useClass: AuthStateProviderAdapter },
|
|
1601
|
-
]
|
|
1531
|
+
];
|
|
1602
1532
|
|
|
1603
1533
|
// OR use the convenience function
|
|
1604
|
-
providers: [
|
|
1605
|
-
...provideAuthProviders(),
|
|
1606
|
-
]
|
|
1534
|
+
providers: [...provideAuthProviders()];
|
|
1607
1535
|
```
|
|
1608
1536
|
|
|
1609
1537
|
### Permissions Not Working
|
|
@@ -1622,107 +1550,255 @@ providers: [
|
|
|
1622
1550
|
|
|
1623
1551
|
### Services
|
|
1624
1552
|
|
|
1625
|
-
| Service
|
|
1626
|
-
|
|
|
1627
|
-
| `ApiResourceService<DTO, T>`
|
|
1628
|
-
| `FileUrlService`
|
|
1629
|
-
| `PermissionValidatorService`
|
|
1630
|
-
| `CookieService`
|
|
1631
|
-
| `PlatformService`
|
|
1553
|
+
| Service | Description |
|
|
1554
|
+
| ---------------------------- | ---------------------------------------------------------------------------------------- |
|
|
1555
|
+
| `ApiResourceService<DTO, T>` | Signal-based CRUD with resource() API (lazy-initialized, accepts optional `serviceName`) |
|
|
1556
|
+
| `FileUrlService` | Cloud storage URL fetching |
|
|
1557
|
+
| `PermissionValidatorService` | Permission state management with wildcards |
|
|
1558
|
+
| `CookieService` | SSR-aware cookie reading |
|
|
1559
|
+
| `PlatformService` | SSR environment detection |
|
|
1632
1560
|
|
|
1633
1561
|
### Classes
|
|
1634
1562
|
|
|
1635
|
-
| Class
|
|
1636
|
-
|
|
|
1637
|
-
| `ApiResourceService`
|
|
1638
|
-
| `BaseFormControl`
|
|
1639
|
-
| `BaseFormPage`
|
|
1640
|
-
| `BaseListPage`
|
|
1563
|
+
| Class | Description |
|
|
1564
|
+
| -------------------- | -------------------------------------------------- |
|
|
1565
|
+
| `ApiResourceService` | Signal-based CRUD base class (alias: `ApiService`) |
|
|
1566
|
+
| `BaseFormControl` | Abstract base for custom form controls |
|
|
1567
|
+
| `BaseFormPage` | Abstract directive for create/edit pages |
|
|
1568
|
+
| `BaseListPage` | Abstract directive for list pages |
|
|
1641
1569
|
|
|
1642
1570
|
### Constants
|
|
1643
1571
|
|
|
1644
|
-
| Constant
|
|
1645
|
-
|
|
|
1646
|
-
| `PERMISSIONS`
|
|
1647
|
-
| `USER_PERMISSIONS`
|
|
1648
|
-
| `ROLE_PERMISSIONS`
|
|
1649
|
-
| `FILE_PERMISSIONS`
|
|
1572
|
+
| Constant | Description |
|
|
1573
|
+
| ------------------- | ----------------------------------------------------- |
|
|
1574
|
+
| `PERMISSIONS` | Aggregated permission codes by module |
|
|
1575
|
+
| `USER_PERMISSIONS` | `{ CREATE, READ, UPDATE, DELETE }` for users |
|
|
1576
|
+
| `ROLE_PERMISSIONS` | `{ CREATE, READ, UPDATE, DELETE }` for roles |
|
|
1577
|
+
| `FILE_PERMISSIONS` | `{ CREATE, READ, UPDATE, DELETE }` for files |
|
|
1650
1578
|
| `FILE_TYPE_FILTERS` | Predefined MIME type arrays (IMAGES, DOCUMENTS, etc.) |
|
|
1651
1579
|
|
|
1652
1580
|
### Components
|
|
1653
1581
|
|
|
1654
|
-
| Component
|
|
1655
|
-
|
|
|
1656
|
-
| `IconComponent`
|
|
1657
|
-
| `LazySelectComponent`
|
|
1658
|
-
| `LazyMultiSelectComponent`
|
|
1659
|
-
| `UserSelectComponent`
|
|
1660
|
-
| `UserMultiSelectComponent`
|
|
1661
|
-
| `FileUploaderComponent`
|
|
1662
|
-
| `FileSelectorDialogComponent` | `lib-file-selector-dialog` | File browser dialog
|
|
1582
|
+
| Component | Selector | Description |
|
|
1583
|
+
| ----------------------------- | -------------------------- | ------------------------- |
|
|
1584
|
+
| `IconComponent` | `lib-icon` | Flexible icon renderer |
|
|
1585
|
+
| `LazySelectComponent` | `lib-lazy-select` | Lazy-loading dropdown |
|
|
1586
|
+
| `LazyMultiSelectComponent` | `lib-lazy-multi-select` | Lazy-loading multi-select |
|
|
1587
|
+
| `UserSelectComponent` | `lib-user-select` | Single user selector |
|
|
1588
|
+
| `UserMultiSelectComponent` | `lib-user-multi-select` | Multiple user selector |
|
|
1589
|
+
| `FileUploaderComponent` | `lib-file-uploader` | Drag & drop file upload |
|
|
1590
|
+
| `FileSelectorDialogComponent` | `lib-file-selector-dialog` | File browser dialog |
|
|
1663
1591
|
|
|
1664
1592
|
### Directives
|
|
1665
1593
|
|
|
1666
|
-
| Directive | Selector
|
|
1667
|
-
| --------------------------------- |
|
|
1668
|
-
| `HasPermissionDirective` | `[hasPermission]`
|
|
1594
|
+
| Directive | Selector | Description |
|
|
1595
|
+
| --------------------------------- | ----------------------------- | --------------------------------- |
|
|
1596
|
+
| `HasPermissionDirective` | `[hasPermission]` | Permission-based rendering |
|
|
1669
1597
|
| `EditModeElementChangerDirective` | `[appEditModeElementChanger]` | Toggle edit mode on form controls |
|
|
1670
|
-
| `IsEmptyImageDirective` | `img`
|
|
1671
|
-
| `PreventDefaultDirective` | `[appPreventDefault]`
|
|
1598
|
+
| `IsEmptyImageDirective` | `img` | Image fallback on error/empty |
|
|
1599
|
+
| `PreventDefaultDirective` | `[appPreventDefault]` | Prevent default event behavior |
|
|
1672
1600
|
|
|
1673
1601
|
### Guards
|
|
1674
1602
|
|
|
1675
|
-
| Guard
|
|
1676
|
-
|
|
|
1677
|
-
| `permissionGuard`
|
|
1678
|
-
| `anyPermissionGuard`
|
|
1679
|
-
| `allPermissionsGuard`
|
|
1603
|
+
| Guard | Description |
|
|
1604
|
+
| --------------------- | ------------------------------------- |
|
|
1605
|
+
| `permissionGuard` | Single permission or ILogicNode check |
|
|
1606
|
+
| `anyPermissionGuard` | OR logic (any of listed permissions) |
|
|
1607
|
+
| `allPermissionsGuard` | AND logic (all of listed permissions) |
|
|
1680
1608
|
|
|
1681
1609
|
### Interfaces
|
|
1682
1610
|
|
|
1683
|
-
| Interface
|
|
1684
|
-
|
|
|
1685
|
-
| `IBaseEntity`
|
|
1686
|
-
| `ILoggedUserInfo`
|
|
1687
|
-
| `IFilterData`
|
|
1688
|
-
| `IFilter`
|
|
1689
|
-
| `IDeleteData`
|
|
1690
|
-
| `IDropDown`
|
|
1691
|
-
| `ISingleResponse<T>`
|
|
1692
|
-
| `IListResponse<T>`
|
|
1693
|
-
| `IBulkResponse<T>`
|
|
1694
|
-
| `IMessageResponse`
|
|
1695
|
-
| `IErrorResponse`
|
|
1696
|
-
| `ILogicNode`
|
|
1697
|
-
| `IUserSelectFilter`
|
|
1698
|
-
| `LoadUsersFn`
|
|
1699
|
-
| `IFileBasicInfo`
|
|
1700
|
-
| `
|
|
1701
|
-
| `
|
|
1702
|
-
| `
|
|
1703
|
-
| `
|
|
1704
|
-
| `
|
|
1705
|
-
| `
|
|
1706
|
-
| `
|
|
1707
|
-
| `
|
|
1708
|
-
| `
|
|
1709
|
-
| `
|
|
1710
|
-
| `
|
|
1711
|
-
| `
|
|
1712
|
-
| `
|
|
1713
|
-
| `
|
|
1611
|
+
| Interface | Description |
|
|
1612
|
+
| ----------------------- | ------------------------------------------------------------------------------ |
|
|
1613
|
+
| `IBaseEntity` | Base entity with ID and timestamps |
|
|
1614
|
+
| `ILoggedUserInfo` | Current user info with company ctx |
|
|
1615
|
+
| `IFilterData` | Filter, pagination, sort payload |
|
|
1616
|
+
| `IFilter` | Filter object (supports arrays) |
|
|
1617
|
+
| `IDeleteData` | Delete request payload |
|
|
1618
|
+
| `IDropDown` | Simple label/value pair |
|
|
1619
|
+
| `ISingleResponse<T>` | Single item response |
|
|
1620
|
+
| `IListResponse<T>` | List with pagination |
|
|
1621
|
+
| `IBulkResponse<T>` | Bulk operation response |
|
|
1622
|
+
| `IMessageResponse` | Message-only response |
|
|
1623
|
+
| `IErrorResponse` | Error with validation details |
|
|
1624
|
+
| `ILogicNode` | Permission logic tree (AND/OR nodes) |
|
|
1625
|
+
| `IUserSelectFilter` | User select filter params |
|
|
1626
|
+
| `LoadUsersFn` | User loading function type |
|
|
1627
|
+
| `IFileBasicInfo` | Basic file info for selectors |
|
|
1628
|
+
| `IFolderBasicInfo` | Folder info for selectors |
|
|
1629
|
+
| `IStorageConfigBasicInfo` | Storage config info for selectors |
|
|
1630
|
+
| `IFileUploadOptions` | Upload options (compression, etc.) |
|
|
1631
|
+
| `IUploadedFile` | Uploaded file response |
|
|
1632
|
+
| `IFileSelectFilter` | File select filter params |
|
|
1633
|
+
| `ISelectFilter` | Filter params for folder/config selectors |
|
|
1634
|
+
| `LoadFilesFn` | File loading function type |
|
|
1635
|
+
| `UploadFileFn` | Single file upload function type |
|
|
1636
|
+
| `UploadMultipleFilesFn` | Multiple files upload function type |
|
|
1637
|
+
| `IFileProvider` | File operations provider interface |
|
|
1638
|
+
| `FilesResponseDto` | File URL service response |
|
|
1639
|
+
| `IAuthStateProvider` | Auth state provider interface |
|
|
1640
|
+
| `IUserListProvider` | User list extensions provider |
|
|
1641
|
+
| `IUserListItem` | Base user for list operations |
|
|
1642
|
+
| `IUserListAction` | User list action definition |
|
|
1643
|
+
| `IUserListColumn` | Extra column for user list |
|
|
1644
|
+
| `IUserListFilter` | User list filter parameters |
|
|
1645
|
+
| `IUserBranchPermission` | User permissions per branch |
|
|
1646
|
+
| `ServiceName` | `'auth' \| 'administration' \| 'iam' \| 'storage' \| 'formBuilder' \| 'email'` |
|
|
1714
1647
|
|
|
1715
1648
|
### Injection Tokens
|
|
1716
1649
|
|
|
1717
|
-
| Token
|
|
1718
|
-
|
|
|
1719
|
-
| `USER_PROVIDER`
|
|
1720
|
-
| `COMPANY_API_PROVIDER`
|
|
1721
|
-
| `USER_PERMISSION_PROVIDER`
|
|
1722
|
-
| `AUTH_STATE_PROVIDER`
|
|
1723
|
-
| `
|
|
1724
|
-
| `PROFILE_PERMISSION_PROVIDER` | `IProfilePermissionProvider` | Yes
|
|
1725
|
-
| `USER_LIST_PROVIDER`
|
|
1650
|
+
| Token | Interface | Optional | Description |
|
|
1651
|
+
| ----------------------------- | ---------------------------- | -------- | ------------------------------------- |
|
|
1652
|
+
| `USER_PROVIDER` | `IUserProvider` | No | User list for IAM |
|
|
1653
|
+
| `COMPANY_API_PROVIDER` | `ICompanyApiProvider` | No | Company list for IAM |
|
|
1654
|
+
| `USER_PERMISSION_PROVIDER` | `IUserPermissionProvider` | No | User permission queries |
|
|
1655
|
+
| `AUTH_STATE_PROVIDER` | `IAuthStateProvider` | No | Auth state for feature packages |
|
|
1656
|
+
| `FILE_PROVIDER` | `IFileProvider` | Yes | File operations (ng-storage) |
|
|
1657
|
+
| `PROFILE_PERMISSION_PROVIDER` | `IProfilePermissionProvider` | Yes | User permissions for profile (ng-iam) |
|
|
1658
|
+
| `USER_LIST_PROVIDER` | `IUserListProvider` | Yes | User list extensions |
|
|
1659
|
+
|
|
1660
|
+
---
|
|
1661
|
+
|
|
1662
|
+
## 10. Translation & Localization
|
|
1663
|
+
|
|
1664
|
+
### Overview
|
|
1665
|
+
|
|
1666
|
+
The ng-shared package provides optional localization support via two modes:
|
|
1667
|
+
|
|
1668
|
+
1. **Fallback-Only Mode** - Hardcoded fallback messages (no API)
|
|
1669
|
+
2. **Full API Mode** - Dynamic translations from API with fallback safety net
|
|
1670
|
+
|
|
1671
|
+
### Fallback-Only Mode
|
|
1672
|
+
|
|
1673
|
+
Use when you want simple hardcoded translations without a localization system:
|
|
1674
|
+
|
|
1675
|
+
```typescript
|
|
1676
|
+
// app.config.ts
|
|
1677
|
+
import { provideFallbackLocalization } from '@flusys/ng-shared';
|
|
1678
|
+
|
|
1679
|
+
export const appConfig: ApplicationConfig = {
|
|
1680
|
+
providers: [
|
|
1681
|
+
// ... other providers
|
|
1682
|
+
...provideFallbackLocalization(), // ← One-liner setup
|
|
1683
|
+
],
|
|
1684
|
+
};
|
|
1685
|
+
```
|
|
1686
|
+
|
|
1687
|
+
**What it provides:**
|
|
1688
|
+
- `FALLBACK_MESSAGES_REGISTRY` - Token for storing hardcoded messages
|
|
1689
|
+
- `TRANSLATE_ADAPTER` - Fallback implementation that reads from registry
|
|
1690
|
+
- Works with route resolvers that register fallback messages
|
|
1691
|
+
|
|
1692
|
+
**Message flow:**
|
|
1693
|
+
```
|
|
1694
|
+
Route with resolveTranslationModule({
|
|
1695
|
+
modules: ['email'],
|
|
1696
|
+
fallbackMessages: EMAIL_MESSAGES
|
|
1697
|
+
})
|
|
1698
|
+
↓
|
|
1699
|
+
Resolver registers in FALLBACK_MESSAGES_REGISTRY
|
|
1700
|
+
↓
|
|
1701
|
+
TRANSLATE_ADAPTER reads from registry
|
|
1702
|
+
↓
|
|
1703
|
+
Components/TranslatePipe render values
|
|
1704
|
+
```
|
|
1705
|
+
|
|
1706
|
+
### Full API Mode
|
|
1707
|
+
|
|
1708
|
+
Use when you need dynamic translations, language switching, and management UI:
|
|
1709
|
+
|
|
1710
|
+
```typescript
|
|
1711
|
+
// app.config.ts
|
|
1712
|
+
import { provideLocalization, getLocalizationConfig } from '@flusys/ng-localization';
|
|
1713
|
+
|
|
1714
|
+
export const appConfig: ApplicationConfig = {
|
|
1715
|
+
providers: [
|
|
1716
|
+
// ... other providers
|
|
1717
|
+
...provideLocalization(
|
|
1718
|
+
getLocalizationConfig({
|
|
1719
|
+
defaultLanguageCode: 'en',
|
|
1720
|
+
enableLayoutSelector: true, // Show language switcher
|
|
1721
|
+
})
|
|
1722
|
+
),
|
|
1723
|
+
],
|
|
1724
|
+
};
|
|
1725
|
+
```
|
|
1726
|
+
|
|
1727
|
+
**What it provides:**
|
|
1728
|
+
- `LocalizationStateService` - Manages current language and translations
|
|
1729
|
+
- `LocalizationApiService` - Fetches translations from backend
|
|
1730
|
+
- `TranslateService` - Registered as `TRANSLATE_ADAPTER`
|
|
1731
|
+
- Language selector component (if `enableLayoutSelector: true`)
|
|
1732
|
+
- Management pages for languages and translations
|
|
1733
|
+
|
|
1734
|
+
### Route Resolver Setup
|
|
1735
|
+
|
|
1736
|
+
All routes should use `resolveTranslationModule` with fallback messages:
|
|
1737
|
+
|
|
1738
|
+
```typescript
|
|
1739
|
+
// email.routes.ts
|
|
1740
|
+
import { resolveTranslationModule, EMAIL_MESSAGES, SHARED_MESSAGES } from '@flusys/ng-shared';
|
|
1741
|
+
|
|
1742
|
+
export const EMAIL_ROUTES: Routes = [
|
|
1743
|
+
{
|
|
1744
|
+
path: '',
|
|
1745
|
+
resolve: {
|
|
1746
|
+
translations: resolveTranslationModule({
|
|
1747
|
+
modules: ['email'],
|
|
1748
|
+
fallbackMessages: { ...EMAIL_MESSAGES, ...SHARED_MESSAGES }
|
|
1749
|
+
})
|
|
1750
|
+
},
|
|
1751
|
+
// ... route definition
|
|
1752
|
+
}
|
|
1753
|
+
];
|
|
1754
|
+
```
|
|
1755
|
+
|
|
1756
|
+
### TranslatePipe Usage
|
|
1757
|
+
|
|
1758
|
+
Works identically in both modes:
|
|
1759
|
+
|
|
1760
|
+
```typescript
|
|
1761
|
+
// Template
|
|
1762
|
+
<h1>{{ 'email.title' | translate }}</h1>
|
|
1763
|
+
<p>{{ 'email.message' | translate: { count: itemCount } }}</p>
|
|
1764
|
+
|
|
1765
|
+
// Component
|
|
1766
|
+
const title = this.translateAdapter.translate('email.title');
|
|
1767
|
+
```
|
|
1768
|
+
|
|
1769
|
+
### Configuration (Full API Mode Only)
|
|
1770
|
+
|
|
1771
|
+
```typescript
|
|
1772
|
+
interface ILocalizationConfig {
|
|
1773
|
+
defaultLanguageCode: string; // Required: 'en', 'ar', etc.
|
|
1774
|
+
loadStrategy?: 'all' | 'modules'; // Default: 'modules'
|
|
1775
|
+
initialModules?: string[]; // Default: []
|
|
1776
|
+
enableLayoutSelector?: boolean; // Default: false
|
|
1777
|
+
}
|
|
1778
|
+
```
|
|
1779
|
+
|
|
1780
|
+
### Key Files
|
|
1781
|
+
|
|
1782
|
+
| File | Purpose |
|
|
1783
|
+
|------|---------|
|
|
1784
|
+
| `providers/fallback-localization.providers.ts` | Fallback mode setup |
|
|
1785
|
+
| `resolvers/translation-module.resolver.ts` | Route resolver for both modes |
|
|
1786
|
+
| `pipes/translate.pipe.ts` | Translation pipe |
|
|
1787
|
+
|
|
1788
|
+
### When to Use Each Mode
|
|
1789
|
+
|
|
1790
|
+
**Use Fallback-Only:**
|
|
1791
|
+
- Simple applications with static content
|
|
1792
|
+
- No language switching needed
|
|
1793
|
+
- No translation management UI
|
|
1794
|
+
- Lightweight setup
|
|
1795
|
+
|
|
1796
|
+
**Use Full API:**
|
|
1797
|
+
- Multi-language support required
|
|
1798
|
+
- User can switch languages
|
|
1799
|
+
- Admin manages translations
|
|
1800
|
+
- Real-time translation updates
|
|
1801
|
+
- Translation management UI needed
|
|
1726
1802
|
|
|
1727
1803
|
## See Also
|
|
1728
1804
|
|
|
@@ -1733,6 +1809,6 @@ providers: [
|
|
|
1733
1809
|
|
|
1734
1810
|
---
|
|
1735
1811
|
|
|
1736
|
-
**Last Updated:** 2026-
|
|
1737
|
-
**Version:** 3.0.
|
|
1812
|
+
**Last Updated:** 2026-03-01
|
|
1813
|
+
**Version:** 3.0.1
|
|
1738
1814
|
**Angular Version:** 21
|