@flusys/ng-shared 4.1.1 → 5.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 +75 -444
- package/fesm2022/flusys-ng-shared.mjs +2199 -2424
- package/fesm2022/flusys-ng-shared.mjs.map +1 -1
- package/package.json +2 -2
- package/types/flusys-ng-shared.d.ts +200 -467
package/README.md
CHANGED
|
@@ -1,75 +1,9 @@
|
|
|
1
1
|
# @flusys/ng-shared
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Shared utilities, provider tokens, base page classes, and reusable components for the FLUSYS Angular platform.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@flusys/ng-shared)
|
|
6
|
-
[](https://www.typescriptlang.org)
|
|
8
|
-
[](LICENSE)
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Table of Contents
|
|
13
|
-
|
|
14
|
-
- [Overview](#overview)
|
|
15
|
-
- [Features](#features)
|
|
16
|
-
- [Compatibility](#compatibility)
|
|
17
|
-
- [Installation](#installation)
|
|
18
|
-
- [Quick Start](#quick-start)
|
|
19
|
-
- [Response Interfaces](#response-interfaces)
|
|
20
|
-
- [Provider Interface Pattern](#provider-interface-pattern)
|
|
21
|
-
- [Injection Tokens](#injection-tokens)
|
|
22
|
-
- [USER_PROVIDER](#user_provider)
|
|
23
|
-
- [COMPANY_PROVIDER](#company_provider)
|
|
24
|
-
- [FILE_PROVIDER](#file_provider)
|
|
25
|
-
- [Base Classes](#base-classes)
|
|
26
|
-
- [ApiResourceService](#apiresourceservice)
|
|
27
|
-
- [BaseListPage](#baselistpage)
|
|
28
|
-
- [BaseFormPage](#baseformpage)
|
|
29
|
-
- [Services](#services)
|
|
30
|
-
- [FileUrlService](#fileurlservice)
|
|
31
|
-
- [PermissionValidatorService](#permissionvalidatorservice)
|
|
32
|
-
- [Reusable Components](#reusable-components)
|
|
33
|
-
- [Directives](#directives)
|
|
34
|
-
- [Pipes](#pipes)
|
|
35
|
-
- [Guards](#guards)
|
|
36
|
-
- [Modules](#modules)
|
|
37
|
-
- [Troubleshooting](#troubleshooting)
|
|
38
|
-
- [License](#license)
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Overview
|
|
43
|
-
|
|
44
|
-
`@flusys/ng-shared` is the second layer in the FLUSYS Angular dependency hierarchy. It depends on `@flusys/ng-core` and provides everything that feature packages (`ng-auth`, `ng-iam`, `ng-storage`, etc.) need to function **independently** of each other.
|
|
45
|
-
|
|
46
|
-
The cornerstone of `ng-shared` is the **Provider Interface Pattern**: injection tokens that define contracts between feature packages. `ng-auth` provides implementations; `ng-iam`, `ng-storage`, and other packages inject the interfaces — never the concrete implementations.
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## Features
|
|
51
|
-
|
|
52
|
-
- ✅ Typed response interfaces (`ISingleResponse`, `IListResponse`, `IBulkResponse`, `IMessageResponse`)
|
|
53
|
-
- ✅ Provider Interface Pattern — `USER_PROVIDER`, `COMPANY_PROVIDER`, `FILE_PROVIDER`
|
|
54
|
-
- ✅ `ApiResourceService` — typed CRUD service base class
|
|
55
|
-
- ✅ `BaseListPage` / `BaseFormPage` — page scaffolding with signal state
|
|
56
|
-
- ✅ `FileUrlService` — safe presigned URL fetching (never construct URLs manually)
|
|
57
|
-
- ✅ `PermissionValidatorService` — client-side permission evaluation
|
|
58
|
-
- ✅ `LazySelectComponent` — virtualized dropdown with server-side search
|
|
59
|
-
- ✅ `FileSelectorDialogComponent` — file picker dialog
|
|
60
|
-
- ✅ `HasPermissionDirective` — structural directive for permission-gated content
|
|
61
|
-
- ✅ `TranslatePipe` — i18n pipe with parameter interpolation
|
|
62
|
-
- ✅ `AngularModule` / `PrimeModule` — pre-aggregated Angular and PrimeNG imports
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
## Compatibility
|
|
67
|
-
|
|
68
|
-
| Package | Version |
|
|
69
|
-
|---------|---------|
|
|
70
|
-
| Angular | 21+ |
|
|
71
|
-
| @flusys/ng-core | 4.x |
|
|
72
|
-
| PrimeNG | 18+ |
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
73
7
|
|
|
74
8
|
---
|
|
75
9
|
|
|
@@ -81,449 +15,146 @@ npm install @flusys/ng-shared @flusys/ng-core
|
|
|
81
15
|
|
|
82
16
|
---
|
|
83
17
|
|
|
84
|
-
##
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
// app.config.ts
|
|
88
|
-
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
89
|
-
import { APP_CONFIG } from '@flusys/ng-core';
|
|
90
|
-
import { environment } from './environments/environment';
|
|
91
|
-
|
|
92
|
-
export const appConfig: ApplicationConfig = {
|
|
93
|
-
providers: [
|
|
94
|
-
{ provide: APP_CONFIG, useValue: environment },
|
|
95
|
-
provideHttpClient(),
|
|
96
|
-
// Feature providers register against ng-shared tokens (see below)
|
|
97
|
-
],
|
|
98
|
-
};
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## Response Interfaces
|
|
104
|
-
|
|
105
|
-
All FLUSYS backend responses conform to one of four shapes:
|
|
106
|
-
|
|
107
|
-
### ISingleResponse\<T\>
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
interface ISingleResponse<T> {
|
|
111
|
-
data: T;
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### IListResponse\<T\>
|
|
116
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
interface IListResponse<T> {
|
|
119
|
-
data: T[];
|
|
120
|
-
total: number;
|
|
121
|
-
page: number;
|
|
122
|
-
pageSize: number;
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### IBulkResponse\<T\>
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
interface IBulkResponse<T> {
|
|
130
|
-
data: T[];
|
|
131
|
-
successCount: number;
|
|
132
|
-
failureCount: number;
|
|
133
|
-
errors?: string[];
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### IMessageResponse
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
interface IMessageResponse {
|
|
141
|
-
message: string;
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### IErrorResponse
|
|
146
|
-
|
|
147
|
-
```typescript
|
|
148
|
-
interface IErrorResponse {
|
|
149
|
-
statusCode: number;
|
|
150
|
-
message: string;
|
|
151
|
-
error?: string;
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
### IListParams (Common Request Shape)
|
|
156
|
-
|
|
157
|
-
```typescript
|
|
158
|
-
interface IListParams {
|
|
159
|
-
page?: number;
|
|
160
|
-
pageSize?: number;
|
|
161
|
-
search?: string;
|
|
162
|
-
sortBy?: string;
|
|
163
|
-
sortOrder?: 'ASC' | 'DESC';
|
|
164
|
-
filters?: Record<string, unknown>;
|
|
165
|
-
}
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
## Provider Interface Pattern
|
|
171
|
-
|
|
172
|
-
Feature packages never import each other directly. Instead, `ng-shared` defines **injection token interfaces** that decouple consumers from providers.
|
|
173
|
-
|
|
174
|
-
```
|
|
175
|
-
ng-auth ──implements──► USER_PROVIDER token ◄──injects── ng-iam
|
|
176
|
-
──implements──► COMPANY_PROVIDER ◄──injects── ng-storage
|
|
177
|
-
ng-storage ──implements──► FILE_PROVIDER ◄──injects── ng-shared components
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
### Injection Tokens
|
|
18
|
+
## 1. ApiResourceService — Signal-Driven CRUD
|
|
181
19
|
|
|
182
|
-
|
|
183
|
-
|-------|-----------|-------------|---------|
|
|
184
|
-
| `USER_PROVIDER` | `IUserProvider` | `ng-auth` | `ng-iam`, `ng-storage` |
|
|
185
|
-
| `COMPANY_PROVIDER` | `ICompanyProvider` | `ng-auth` | `ng-storage`, `ng-notification` |
|
|
186
|
-
| `FILE_PROVIDER` | `IFileProvider` | `ng-storage` | `ng-shared` components |
|
|
187
|
-
| `LAYOUT_AUTH_STATE` | `ILayoutAuthState` | `ng-auth` | `ng-layout` |
|
|
188
|
-
| `LAYOUT_AUTH_API` | `ILayoutAuthApi` | `ng-auth` | `ng-layout` |
|
|
189
|
-
| `LAYOUT_NOTIFICATION_BELL` | `INotificationBellProvider` | `ng-notification` | `ng-layout` |
|
|
190
|
-
| `LAYOUT_LANGUAGE_SELECTOR` | `ILanguageSelectorProvider` | `ng-localization` | `ng-layout` |
|
|
191
|
-
|
|
192
|
-
### USER_PROVIDER
|
|
20
|
+
Extend this class to get a reactive list resource backed by `resource()`, with pagination, search, and all standard POST-only RPC endpoints.
|
|
193
21
|
|
|
194
22
|
```typescript
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
23
|
+
import { Injectable, inject } from '@angular/core';
|
|
24
|
+
import { HttpClient } from '@angular/common/http';
|
|
25
|
+
import { ApiResourceService } from '@flusys/ng-shared';
|
|
199
26
|
|
|
200
|
-
|
|
201
|
-
|
|
27
|
+
export interface IProduct { id: string; name: string; price: number; }
|
|
28
|
+
export interface IProductDto { name: string; price: number; }
|
|
202
29
|
|
|
203
30
|
@Injectable({ providedIn: 'root' })
|
|
204
|
-
export class
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
getCurrentUserId(): string | null {
|
|
208
|
-
return this.userProvider.user()?.id ?? null;
|
|
31
|
+
export class ProductService extends ApiResourceService<IProductDto, IProduct> {
|
|
32
|
+
constructor() {
|
|
33
|
+
super('products', inject(HttpClient), 'administration'); // moduleApiName, http, serviceName (key in APP_CONFIG.services)
|
|
209
34
|
}
|
|
210
35
|
}
|
|
211
36
|
```
|
|
212
37
|
|
|
213
|
-
|
|
38
|
+
Using the reactive list in a component:
|
|
214
39
|
|
|
215
40
|
```typescript
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
companyId: Signal<string | null>;
|
|
220
|
-
}
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### FILE_PROVIDER
|
|
224
|
-
|
|
225
|
-
```typescript
|
|
226
|
-
interface IFileProvider {
|
|
227
|
-
getFileUrl(fileId: string): Observable<string | null>;
|
|
228
|
-
uploadFile(file: File, folder?: string): Observable<IUploadedFile>;
|
|
229
|
-
}
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
---
|
|
233
|
-
|
|
234
|
-
## Base Classes
|
|
235
|
-
|
|
236
|
-
### ApiResourceService
|
|
237
|
-
|
|
238
|
-
Generic typed CRUD service that maps to FLUSYS POST-only RPC endpoints. Extend this to create a fully-typed API service in seconds.
|
|
41
|
+
@Component({ ... })
|
|
42
|
+
export class ProductListComponent {
|
|
43
|
+
private readonly svc = inject(ProductService);
|
|
239
44
|
|
|
240
|
-
|
|
241
|
-
|
|
45
|
+
readonly items = this.svc.data; // Signal<IProduct[]>
|
|
46
|
+
readonly total = this.svc.total; // Signal<number>
|
|
47
|
+
readonly loading = this.svc.isLoading; // Signal<boolean>
|
|
242
48
|
|
|
243
|
-
|
|
244
|
-
export class ProductApiService extends ApiResourceService<Product, CreateProductDto, UpdateProductDto> {
|
|
245
|
-
protected override resource = 'product';
|
|
246
|
-
}
|
|
49
|
+
ngOnInit() { this.svc.fetchList(); }
|
|
247
50
|
|
|
248
|
-
|
|
249
|
-
// - getAll(params): Observable<IListResponse<Product>>
|
|
250
|
-
// - getById(id): Observable<ISingleResponse<Product>>
|
|
251
|
-
// - insert(dto): Observable<ISingleResponse<Product>>
|
|
252
|
-
// - update(dto): Observable<ISingleResponse<Product>>
|
|
253
|
-
// - delete(id): Observable<IMessageResponse>
|
|
254
|
-
// - bulkDelete(ids): Observable<IBulkResponse<Product>>
|
|
255
|
-
```
|
|
51
|
+
onSearch(q: string) { this.svc.fetchList(q); }
|
|
256
52
|
|
|
257
|
-
|
|
53
|
+
onPageChange(page: number, size: number) {
|
|
54
|
+
this.svc.setPagination({ currentPage: page, pageSize: size });
|
|
55
|
+
}
|
|
258
56
|
|
|
259
|
-
|
|
57
|
+
async onCreate(dto: IProductDto) {
|
|
58
|
+
await this.svc.insert(dto);
|
|
59
|
+
this.svc.reload();
|
|
60
|
+
}
|
|
260
61
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
selector: 'app-product-list',
|
|
266
|
-
templateUrl: './product-list.component.html',
|
|
267
|
-
})
|
|
268
|
-
export class ProductListComponent extends BaseListPage<Product> {
|
|
269
|
-
protected apiService = inject(ProductApiService);
|
|
270
|
-
|
|
271
|
-
// Inherited signals:
|
|
272
|
-
// items Signal<Product[]>
|
|
273
|
-
// total Signal<number>
|
|
274
|
-
// page Signal<number>
|
|
275
|
-
// pageSize Signal<number>
|
|
276
|
-
// isLoading Signal<boolean>
|
|
277
|
-
// searchQuery Signal<string>
|
|
62
|
+
async onDelete(id: string) {
|
|
63
|
+
await this.svc.delete({ id });
|
|
64
|
+
this.svc.reload();
|
|
65
|
+
}
|
|
278
66
|
}
|
|
279
67
|
```
|
|
280
68
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
Scaffold for create/edit forms with validation state and submission handling.
|
|
284
|
-
|
|
285
|
-
```typescript
|
|
286
|
-
import { BaseFormPage } from '@flusys/ng-shared';
|
|
287
|
-
|
|
288
|
-
@Component({
|
|
289
|
-
selector: 'app-product-form',
|
|
290
|
-
templateUrl: './product-form.component.html',
|
|
291
|
-
})
|
|
292
|
-
export class ProductFormComponent extends BaseFormPage<Product> {
|
|
293
|
-
protected apiService = inject(ProductApiService);
|
|
294
|
-
|
|
295
|
-
// Inherited signals:
|
|
296
|
-
// isSaving Signal<boolean>
|
|
297
|
-
// isEditMode Signal<boolean>
|
|
298
|
-
// formErrors Signal<Record<string, string>>
|
|
299
|
-
}
|
|
300
|
-
```
|
|
69
|
+
Available CRUD methods: `insert`, `insertMany`, `findById`, `findByIds`, `getAll`, `getByFilter`, `update`, `updateMany`, `bulkUpsert`, `delete`.
|
|
301
70
|
|
|
302
71
|
---
|
|
303
72
|
|
|
304
|
-
##
|
|
305
|
-
|
|
306
|
-
### FileUrlService
|
|
73
|
+
## 2. FileUrlService — Presigned URL Fetching
|
|
307
74
|
|
|
308
|
-
|
|
75
|
+
Never construct file URLs manually. `FileUrlService` handles S3/Azure presigned URLs, caching, and deduplication.
|
|
309
76
|
|
|
310
77
|
```typescript
|
|
311
78
|
import { FileUrlService } from '@flusys/ng-shared';
|
|
312
79
|
|
|
313
80
|
@Component({ ... })
|
|
314
81
|
export class AvatarComponent {
|
|
315
|
-
private fileUrlService = inject(FileUrlService);
|
|
316
|
-
avatarUrl = signal<string | null>(null);
|
|
82
|
+
private readonly fileUrlService = inject(FileUrlService);
|
|
83
|
+
readonly avatarUrl = signal<string | null>(null);
|
|
317
84
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
85
|
+
ngOnInit() {
|
|
86
|
+
this.fileUrlService.fetchSingleFileUrl('file-id-123').subscribe(file => {
|
|
87
|
+
this.avatarUrl.set(file?.url ?? null);
|
|
88
|
+
});
|
|
321
89
|
}
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
```typescript
|
|
326
|
-
// ❌ WRONG — never construct file URLs manually
|
|
327
|
-
const url = `${apiBaseUrl}/storage/${fileId}`;
|
|
328
|
-
|
|
329
|
-
// ✅ CORRECT — always use FileUrlService
|
|
330
|
-
const file = await this.fileUrlService.fetchSingleFileUrl(fileId);
|
|
331
|
-
const url = file?.url ?? null;
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
**Methods:**
|
|
335
|
-
|
|
336
|
-
| Method | Description |
|
|
337
|
-
|--------|-------------|
|
|
338
|
-
| `fetchSingleFileUrl(fileId)` | Fetch presigned URL for a single file |
|
|
339
|
-
| `fetchMultipleFileUrls(fileIds)` | Batch fetch presigned URLs |
|
|
340
|
-
| `clearCache(fileId?)` | Invalidate cached URL(s) |
|
|
341
|
-
|
|
342
|
-
### PermissionValidatorService
|
|
343
|
-
|
|
344
|
-
Client-side permission evaluation without direct ng-iam dependency.
|
|
345
|
-
|
|
346
|
-
```typescript
|
|
347
|
-
import { PermissionValidatorService } from '@flusys/ng-shared';
|
|
348
90
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
91
|
+
// Batch fetch — only requests IDs not already cached
|
|
92
|
+
loadMultiple(ids: string[]) {
|
|
93
|
+
this.fileUrlService.fetchFileUrls(ids).subscribe(files => { /* ... */ });
|
|
94
|
+
}
|
|
352
95
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
96
|
+
// Synchronous read from cache after a prior fetch
|
|
97
|
+
getCachedUrl(id: string): string | null {
|
|
98
|
+
return this.fileUrlService.getFileUrl(id);
|
|
99
|
+
}
|
|
356
100
|
}
|
|
357
101
|
```
|
|
358
102
|
|
|
359
103
|
---
|
|
360
104
|
|
|
361
|
-
##
|
|
362
|
-
|
|
363
|
-
### LazySelectComponent
|
|
364
|
-
|
|
365
|
-
Virtualized dropdown with server-side search. Ideal for large datasets.
|
|
366
|
-
|
|
367
|
-
```html
|
|
368
|
-
<flusys-lazy-select
|
|
369
|
-
[apiService]="productApiService"
|
|
370
|
-
[labelField]="'name'"
|
|
371
|
-
[valueField]="'id'"
|
|
372
|
-
[(ngModel)]="selectedProductId"
|
|
373
|
-
placeholder="Search products..."
|
|
374
|
-
/>
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
### FileSelectorDialogComponent
|
|
378
|
-
|
|
379
|
-
File picker dialog that integrates with `@flusys/ng-storage`.
|
|
380
|
-
|
|
381
|
-
```html
|
|
382
|
-
<flusys-file-selector
|
|
383
|
-
[accept]="'image/*'"
|
|
384
|
-
[maxSize]="5242880"
|
|
385
|
-
(fileSelected)="onFileSelected($event)"
|
|
386
|
-
/>
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
---
|
|
390
|
-
|
|
391
|
-
## Directives
|
|
392
|
-
|
|
393
|
-
### HasPermissionDirective
|
|
394
|
-
|
|
395
|
-
Structural directive that removes elements from the DOM if the user lacks the required permission:
|
|
396
|
-
|
|
397
|
-
```html
|
|
398
|
-
<!-- Single permission -->
|
|
399
|
-
<button *hasPermission="'product:delete'">Delete</button>
|
|
400
|
-
|
|
401
|
-
<!-- Multiple permissions (AND logic) -->
|
|
402
|
-
<div *hasPermission="['product:update', 'product:read']">Edit Panel</div>
|
|
105
|
+
## 3. PermissionValidatorService + HasPermissionDirective
|
|
403
106
|
|
|
404
|
-
|
|
405
|
-
|
|
107
|
+
```typescript
|
|
108
|
+
const pv = inject(PermissionValidatorService);
|
|
406
109
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
<span>No access</span>
|
|
411
|
-
</ng-template>
|
|
110
|
+
pv.hasPermission('user.create'); // true
|
|
111
|
+
pv.hasPermission('report.export'); // true — wildcard match
|
|
112
|
+
pv.isPermissionsLoaded(); // boolean
|
|
412
113
|
```
|
|
413
114
|
|
|
414
|
-
---
|
|
415
|
-
|
|
416
|
-
## Pipes
|
|
417
|
-
|
|
418
|
-
### TranslatePipe
|
|
419
|
-
|
|
420
|
-
Translate i18n keys with optional parameter interpolation:
|
|
421
|
-
|
|
422
115
|
```html
|
|
423
|
-
<!--
|
|
424
|
-
<
|
|
425
|
-
|
|
426
|
-
<!-- With parameters -->
|
|
427
|
-
<span>{{ 'pagination.showing' | translate: { from: 1, to: 10, total: 100 } }}</span>
|
|
116
|
+
<!-- Removes element from DOM when permission fails -->
|
|
117
|
+
<button *hasPermission="'user.create'">Create User</button>
|
|
428
118
|
```
|
|
429
119
|
|
|
430
|
-
The pipe automatically subscribes to language change events and re-renders on switch.
|
|
431
|
-
|
|
432
120
|
---
|
|
433
121
|
|
|
434
|
-
## Guards
|
|
435
|
-
|
|
436
|
-
| Guard | Description |
|
|
437
|
-
|-------|-------------|
|
|
438
|
-
| `AuthenticatedGuard` | Redirects unauthenticated users to `/auth/login` |
|
|
439
|
-
| `GuestGuard` | Redirects authenticated users away from auth pages |
|
|
440
|
-
| `PermissionGuard` | Blocks route if user lacks required permission |
|
|
122
|
+
## 4. Permission Guards
|
|
441
123
|
|
|
442
124
|
```typescript
|
|
443
|
-
|
|
125
|
+
import { permissionGuard, anyPermissionGuard, allPermissionsGuard } from '@flusys/ng-shared';
|
|
126
|
+
|
|
444
127
|
export const routes: Routes = [
|
|
445
128
|
{
|
|
446
|
-
path: '
|
|
447
|
-
canActivate: [
|
|
448
|
-
loadComponent: () => import('./
|
|
129
|
+
path: 'users',
|
|
130
|
+
canActivate: [permissionGuard('user.view')], // single
|
|
131
|
+
loadComponent: () => import('./user-list.component'),
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
path: 'reports',
|
|
135
|
+
canActivate: [anyPermissionGuard(['report.view', 'report.export'])], // OR
|
|
136
|
+
loadComponent: () => import('./reports.component'),
|
|
449
137
|
},
|
|
450
138
|
{
|
|
451
139
|
path: 'admin',
|
|
452
|
-
canActivate: [
|
|
453
|
-
|
|
454
|
-
loadComponent: () => import('./pages/admin.component'),
|
|
140
|
+
canActivate: [allPermissionsGuard(['admin.view', 'admin.manage'])], // AND
|
|
141
|
+
loadComponent: () => import('./admin.component'),
|
|
455
142
|
},
|
|
456
143
|
];
|
|
457
144
|
```
|
|
458
145
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
## Modules
|
|
462
|
-
|
|
463
|
-
### AngularModule
|
|
464
|
-
|
|
465
|
-
Pre-aggregated Angular common imports for use in components:
|
|
466
|
-
|
|
467
|
-
```typescript
|
|
468
|
-
import { AngularModule } from '@flusys/ng-shared';
|
|
469
|
-
|
|
470
|
-
@Component({
|
|
471
|
-
imports: [AngularModule],
|
|
472
|
-
// Includes: CommonModule, FormsModule, ReactiveFormsModule, RouterModule, etc.
|
|
473
|
-
})
|
|
474
|
-
export class MyComponent {}
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
### PrimeModule
|
|
478
|
-
|
|
479
|
-
Pre-aggregated PrimeNG component imports:
|
|
480
|
-
|
|
481
|
-
```typescript
|
|
482
|
-
import { PrimeModule } from '@flusys/ng-shared';
|
|
483
|
-
|
|
484
|
-
@Component({
|
|
485
|
-
imports: [PrimeModule],
|
|
486
|
-
// Includes: ButtonModule, TableModule, DialogModule, InputTextModule, etc.
|
|
487
|
-
})
|
|
488
|
-
export class MyComponent {}
|
|
489
|
-
```
|
|
146
|
+
All three guards redirect to `/` by default. Pass a second argument to change it: `permissionGuard('user.view', '/access-denied')`.
|
|
490
147
|
|
|
491
148
|
---
|
|
492
149
|
|
|
493
|
-
##
|
|
494
|
-
|
|
495
|
-
**`No provider for USER_PROVIDER`**
|
|
496
|
-
|
|
497
|
-
You need to register the auth providers in `app.config.ts`:
|
|
498
|
-
|
|
499
|
-
```typescript
|
|
500
|
-
import { provideAuthProviders } from '@flusys/ng-auth';
|
|
501
|
-
|
|
502
|
-
providers: [
|
|
503
|
-
...provideAuthProviders(),
|
|
504
|
-
]
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
**`FileUrlService returns null for valid fileId`**
|
|
150
|
+
## 5. TranslatePipe
|
|
508
151
|
|
|
509
|
-
|
|
152
|
+
```html
|
|
153
|
+
{{ 'shared.save' | translate }}
|
|
510
154
|
|
|
511
|
-
|
|
512
|
-
// Ensure storage is enabled in APP_CONFIG
|
|
513
|
-
services: {
|
|
514
|
-
storage: { enabled: true }
|
|
515
|
-
}
|
|
155
|
+
{{ 'pagination.showing' | translate: { from: 1, to: 10 } }}
|
|
516
156
|
```
|
|
517
157
|
|
|
518
|
-
**`TranslatePipe shows raw key (e.g., "common.save")`**
|
|
519
|
-
|
|
520
|
-
Localization isn't initialized or the key doesn't exist in translation files. Provide `TRANSLATE_ADAPTER` in `app.config.ts` via `provideLocalization()`.
|
|
521
|
-
|
|
522
|
-
**`HasPermissionDirective always hides content`**
|
|
523
|
-
|
|
524
|
-
`PermissionValidatorService` has no data — ensure `ng-iam` is enabled and permissions are loaded after login.
|
|
525
|
-
|
|
526
|
-
---
|
|
527
158
|
|
|
528
159
|
## License
|
|
529
160
|
|