@flusys/ng-shared
v4.0.1
Published
Shared components and utilities for FLUSYS Angular packages
Readme
@flusys/ng-shared Package Guide
Overview
@flusys/ng-shared provides reusable components, directives, API services, and utilities used across all FLUSYS applications. This package extends ng-core with UI components, API integration patterns, and provider interfaces for package independence.
Key Principle: ng-shared depends only on ng-core, never on ng-layout or feature packages.
Package Information
- Package:
@flusys/ng-shared - Version: 4.0.1
- Dependencies: ng-core
- Dependents: ng-layout, ng-auth, ng-iam, ng-storage, flusysng
- Build Command:
npm run build:ng-shared
1. Interfaces
Data Interfaces
IBaseEntity
Base entity interface matching backend Identity entity.
interface IBaseEntity {
id: string;
createdAt: Date;
updatedAt: Date;
deletedAt?: Date | null;
createdById?: string | null;
updatedById?: string | null;
deletedById?: string | null;
}Entity Mixins:
| Interface | Fields | Purpose |
| ---------------- | ------------------------------------ | -------------------- |
| ISoftDeletable | deletedAt?: Date \| null | Soft delete support |
| ITimestampable | createdAt, updatedAt | Timestamp tracking |
| IActivatable | isActive: boolean | Active status toggle |
| IOrderable | serial?: number \| null | Ordering/sorting |
| IMetadata | metadata?: Record<string, unknown> | JSON metadata field |
ILoggedUserInfo
Current logged-in user info with optional company context.
interface ILoggedUserInfo {
id: string;
email: string;
name?: string;
phone?: string;
profilePictureId?: string;
companyId?: string;
branchId?: string;
companyLogoId?: string;
branchLogoId?: string;
}IFilterData
Filter, pagination, and sort payload for list queries.
interface IPagination {
pageSize: number;
currentPage: number;
}
interface ISort {
[key: string]: "ASC" | "DESC";
}
// Filter supports primitives and arrays for multi-value filtering
interface IFilter {
[key: string]: string | number | boolean | null | undefined | string[] | number[];
}
interface IFilterData {
filter?: IFilter;
pagination?: IPagination;
select?: string[];
sort?: ISort;
withDeleted?: boolean;
extraKey?: string[];
}Note: IFilter supports array values (string[], number[]) for multi-value filtering (e.g., filtering by multiple status values or IDs).
IDeleteData
Delete request payload matching backend DeleteDto.
type DeleteType = "delete" | "restore" | "permanent";
interface IDeleteData {
id: string | string[]; // Single or batch delete
type: DeleteType;
}IDropDown
Simple dropdown item for select components.
interface IDropDown {
label: string;
value: string;
}User Select Interfaces
interface IUserSelectFilter {
page: number;
pageSize: number;
search: string;
}
type LoadUsersFn = (filter: IUserSelectFilter) => Observable<IListResponse<IUserBasicInfo>>;File Select Interfaces
interface IFileBasicInfo {
id: string;
name: string;
contentType: string;
size: string;
url: string | null;
}
interface IFileUploadOptions {
storageConfigId?: string;
folderPath?: string;
maxWidth?: number;
maxHeight?: number;
quality?: number;
compress?: boolean;
}
interface IUploadedFile {
id?: string; // File manager ID (UUID) - available when registered
name: string;
key: string;
size: number;
contentType: string;
}
interface IFileSelectFilter {
page: number;
pageSize: number;
search: string;
contentTypes?: string[];
folderId?: string;
}
type LoadFilesFn = (filter: IFileSelectFilter) => Observable<IListResponse<IFileBasicInfo>>;
type UploadFileFn = (file: File, options?: IFileUploadOptions) => Observable<ISingleResponse<IUploadedFile>>;
type GetFileUrlsFn = (fileIds: string[]) => Observable<ISingleResponse<IFileBasicInfo[]>>;File Type Filters (Constants):
import { FILE_TYPE_FILTERS, getAcceptString, isFileTypeAllowed, getFileIconClass, formatFileSize } from "@flusys/ng-shared";
FILE_TYPE_FILTERS.IMAGES; // ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml']
FILE_TYPE_FILTERS.DOCUMENTS; // ['application/pdf', 'application/msword', ...]
FILE_TYPE_FILTERS.VIDEOS; // ['video/mp4', 'video/webm', ...]
FILE_TYPE_FILTERS.AUDIO; // ['audio/mpeg', 'audio/wav', ...]
FILE_TYPE_FILTERS.ALL; // [] (allows all)
getAcceptString(["image/*"]); // 'image/*'
isFileTypeAllowed(file, ["image/*"]); // true/false
getFileIconClass("image/png"); // 'pi pi-image'
formatFileSize(1024); // '1 KB'Response Interfaces
Type-safe interfaces matching FLUSYS_NEST backend response DTOs.
// Single item response (POST /resource/insert, /update, /get/:id)
interface ISingleResponse<T> {
success: boolean;
message: string;
data?: T;
_meta?: IRequestMeta;
}
// List response with pagination (POST /resource/get-all)
interface IListResponse<T> {
success: boolean;
message: string;
data?: T[];
meta: IPaginationMeta;
_meta?: IRequestMeta;
}
// Bulk operation response (POST /resource/insert-many, /update-many)
interface IBulkResponse<T> {
success: boolean;
message: string;
data?: T[];
meta: IBulkMeta;
_meta?: IRequestMeta;
}
// Message-only response (POST /resource/delete, /logout)
interface IMessageResponse {
success: boolean;
message: string;
_meta?: IRequestMeta;
}
// Error response (validation errors, exceptions)
interface IErrorResponse {
success: false;
message: string;
code?: string;
errors?: IValidationError[];
_meta?: IRequestMeta;
}
// Union type
type ApiResponse<T> = ISingleResponse<T> | IListResponse<T> | IBulkResponse<T> | IMessageResponse | IErrorResponse;Metadata types:
| Interface | Fields |
| ------------------ | ----------------------------------------------------- |
| IRequestMeta | requestId?, timestamp?, responseTime? |
| IPaginationMeta | total, page, pageSize, count, hasMore?, totalPages? |
| IBulkMeta | count, failed?, total? |
| IValidationError | field, message, constraint? |
Auth & Storage Response Types
interface ILoginResponse {
success: boolean;
message: string;
data: { accessToken: string; refreshToken: string; user: ILoginUserData };
}
interface IRefreshTokenResponse {
success: boolean;
message: string;
data: { accessToken: string; refreshToken?: string };
}
interface IFileData {
id: string;
name: string;
originalName: string;
contentType: string;
size: number;
key: string;
url?: string;
thumbnailUrl?: string;
createdAt: Date;
}
// File URL service response DTO
interface FilesResponseDto {
id: string;
name: string;
contentType: string;
url: string | null;
}Permission Interfaces
Discriminated union for building complex permission logic trees.
// Single permission check
interface IActionNode {
type: "action";
actionId: string;
}
// Group with AND/OR logic
interface IGroupNode {
type: "group";
operator: "AND" | "OR";
children: ILogicNode[];
}
// Union type
type ILogicNode = IActionNode | IGroupNode;2. Enums
enum ContactTypeEnum {
PHONE = 1,
EMAIL = 2,
}
enum IconTypeEnum {
PRIMENG_ICON = 1,
IMAGE_FILE_LINK = 2,
DIRECT_TAG_SVG = 3,
}3. Constants
Permission Constants
Centralized permission codes for type-safe permission checks. Single source of truth to prevent typos.
import { PERMISSIONS, USER_PERMISSIONS, ROLE_PERMISSIONS } from '@flusys/ng-shared';
// Use constants instead of strings
*hasPermission="PERMISSIONS.USER.READ"
// Individual permission groups available:
USER_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
COMPANY_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
BRANCH_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
ACTION_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
ROLE_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
ROLE_ACTION_PERMISSIONS // { READ, ASSIGN }
USER_ROLE_PERMISSIONS // { READ, ASSIGN }
USER_ACTION_PERMISSIONS // { READ, ASSIGN }
COMPANY_ACTION_PERMISSIONS // { READ, ASSIGN }
FILE_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
FOLDER_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
STORAGE_CONFIG_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
EMAIL_CONFIG_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
EMAIL_TEMPLATE_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
FORM_PERMISSIONS // { CREATE, READ, UPDATE, DELETE }
// Aggregated object with all permissions
PERMISSIONS.USER.READ // 'user.read'
PERMISSIONS.ROLE.CREATE // 'role.create'
PERMISSIONS.FILE.DELETE // 'file.delete'Type: PermissionCode - Union type of all valid permission code strings.
4. Services
ApiResourceService
Signal-based CRUD service using Angular 21 resource() API with lazy initialization. All endpoints use POST (RPC-style).
ServiceName Type:
type ServiceName = "auth" | "administration" | "iam" | "storage" | "formBuilder" | "email";Define a service:
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { ApiResourceService } from "@flusys/ng-shared";
@Injectable({ providedIn: "root" })
export class UserService extends ApiResourceService<UserDto, IUser> {
constructor(http: HttpClient) {
// Option 1: Use global apiBaseUrl (default)
super("users", http);
// Base URL: APP_CONFIG.apiBaseUrl + '/users'
// Option 2: Use feature-specific service URL (recommended)
super("users", http, "administration");
// Base URL: APP_CONFIG.services.administration.baseUrl + '/users'
}
}Constructor Parameters:
| Parameter | Type | Required | Description |
| --------------- | ------------- | -------- | ------------------------------------------------ |
| moduleApiName | string | Yes | API path segment (e.g., 'users', 'file-manager') |
| http | HttpClient | Yes | Angular HttpClient instance |
| serviceName | ServiceName | No | Feature service name for URL resolution |
Service URL Resolution:
- If
serviceNameis provided, usesgetServiceUrl(config, serviceName)to resolve the base URL - Falls back to
APP_CONFIG.apiBaseUrlif service not found orserviceNamenot provided
Lazy Initialization:
The list resource is lazy-initialized to avoid unnecessary HTTP requests on service construction. The resource is only created when first needed:
// Resource is NOT created until one of these is called:
userService.fetchList("", { pagination: { currentPage: 0, pageSize: 10 } });
// OR
userService.initListResource(); // Explicit initializationInternal resource structure:
private _listResource: ResourceRef<IListResponse<InterfaceT> | undefined> | null = null;
private _resourceInitialized = false;
private readonly _resourceInitSignal = signal(false);
// Initialize the list resource (called automatically by fetchList)
initListResource(): void {
if (this._resourceInitialized) return;
this._resourceInitialized = true;
this._resourceInitSignal.set(true);
// Creates the resource with linkedSignal for data, isLoading, etc.
}Endpoint mapping:
| Method | HTTP | Endpoint | Response |
| ------------------------ | ---- | ------------------------- | -------------------- |
| insert(dto) | POST | /{resource}/insert | ISingleResponse<T> |
| insertMany(dtos) | POST | /{resource}/insert-many | IBulkResponse<T> |
| findById(id, select?) | POST | /{resource}/get/:id | ISingleResponse<T> |
| getAll(search, filter) | POST | /{resource}/get-all?q= | IListResponse<T> |
| update(dto) | POST | /{resource}/update | ISingleResponse<T> |
| updateMany(dtos) | POST | /{resource}/update-many | IBulkResponse<T> |
| delete(deleteDto) | POST | /{resource}/delete | IMessageResponse |
All methods above return Observable. Async (Promise) variants are also available: insertAsync, insertManyAsync, findByIdAsync, updateAsync, updateManyAsync, deleteAsync.
Reactive signals:
| Signal | Type | Description |
| ------------ | ----------------------------- | ----------------------- |
| isLoading | Signal<boolean> | Whether data is loading |
| data | Signal<T[]> | Current list data |
| total | Signal<number> | Total item count |
| pageInfo | Signal<IPaginationMeta> | Pagination metadata |
| hasMore | Signal<boolean> | More pages available |
| searchTerm | WritableSignal<string> | Current search term |
| filterData | WritableSignal<IFilterData> | Filter/pagination state |
List management:
// Trigger data fetch (updates searchTerm and filterData signals, resource auto-reloads)
userService.fetchList("search term", { pagination: { currentPage: 0, pageSize: 10 } });
// Pagination helpers
userService.setPagination({ currentPage: 1, pageSize: 20 });
userService.nextPage();
userService.resetPagination();
userService.reload();Component usage:
@Component({...})
export class UserListComponent {
private readonly userService = inject(UserService);
readonly users = this.userService.data; // Signal<IUser[]>
readonly isLoading = this.userService.isLoading; // Signal<boolean>
readonly total = this.userService.total; // Signal<number>
constructor() {
this.userService.fetchList('', { pagination: { currentPage: 0, pageSize: 10 } });
}
async createUser(dto: UserDto) {
await this.userService.insertAsync(dto);
this.userService.reload(); // Refresh list
}
}Note:
ApiServiceis exported as an alias forApiResourceServicefor backward compatibility.
FileUrlService
Fetches file URLs from the backend. Supports presigned URLs for cloud storage (S3, Azure).
CRITICAL: Never construct file URLs manually. Always use this service.
import { FileUrlService } from '@flusys/ng-shared';
@Component({...})
export class ProductComponent {
private readonly fileUrlService = inject(FileUrlService);
loadImage(fileId: string) {
// Fetch from backend: POST /file-manager/get-files
this.fileUrlService.fetchSingleFileUrl(fileId).subscribe(file => {
this.imageUrl.set(file?.url ?? null);
});
}
loadMultiple(fileIds: string[]) {
this.fileUrlService.fetchFileUrls(fileIds).subscribe(files => {
// files: FilesResponseDto[] with { id, name, contentType, url }
});
}
}Methods:
| Method | Returns | Description |
| ---------------------------- | -------------------------------------- | --------------------------------- |
| getFileUrl(fileId) | string \| null | Get cached URL (synchronous) |
| fileUrlSignal(fileId) | Signal<string \| null> | Computed signal from cache |
| fetchFileUrls(fileIds[]) | Observable<FilesResponseDto[]> | Fetch from backend, updates cache |
| fetchSingleFileUrl(fileId) | Observable<FilesResponseDto \| null> | Fetch single file |
| clearCache() | void | Clear all cached URLs |
| removeFromCache(fileId) | void | Remove specific entry from cache |
PermissionValidatorService
Signal-based permission state management. Used by HasPermissionDirective, permission guards, and IAM. Supports wildcard permissions.
import { PermissionValidatorService } from '@flusys/ng-shared';
@Component({...})
export class MyComponent {
private readonly permissionValidator = inject(PermissionValidatorService);
ngOnInit() {
// Set permissions (typically done by IAM PermissionStateService)
this.permissionValidator.setPermissions(['user.view', 'user.create']);
// Check single permission
if (this.permissionValidator.hasPermission('user.view')) {
// User has permission
}
// Check loaded state (signal-based)
if (this.permissionValidator.isLoaded()) {
// Permissions have been loaded
}
}
}Methods:
| Method | Returns | Description |
| ------------------------- | --------- | -------------------------------------------- |
| setPermissions(codes[]) | void | Replace all permissions |
| clearPermissions() | void | Clear all permissions |
| hasPermission(code) | boolean | Check single permission (supports wildcards) |
| isPermissionsLoaded() | boolean | Deprecated - Use isLoaded() signal |
Signals:
| Signal | Type | Description |
| ------------- | ------------------ | --------------------------------- |
| permissions | Signal<string[]> | Readonly current permissions |
| isLoaded | Signal<boolean> | Whether permissions have been set |
Wildcard Support:
The hasPermission() method uses the permission evaluator utility which supports wildcards:
// Exact match
hasPermission("user.read"); // true if permissions include 'user.read'
// Global wildcard
hasPermission("user.read"); // true if permissions include '*'
// Module wildcard
hasPermission("user.read"); // true if permissions include 'user.*'CookieService
SSR-aware cookie reading service.
import { CookieService } from "@flusys/ng-shared";
const cookies = inject(CookieService).get(); // Returns document.cookie (browser) or request header cookie (server)PlatformService
SSR environment detection service.
import { PlatformService } from "@flusys/ng-shared";
const platform = inject(PlatformService);
if (!platform.isServer) {
// Browser-only code (localStorage, window, etc.)
}5. Components
IconComponent
Flexible icon renderer supporting PrimeNG icons, image files, and SVG.
- Selector:
lib-icon - Inputs:
icon(required string),iconType(optionalIconTypeEnum, default:PRIMENG_ICON)
<!-- PrimeNG icon (default) -->
<lib-icon icon="pi pi-user" />
<!-- Image file -->
<lib-icon icon="/assets/logo.png" [iconType]="IconTypeEnum.IMAGE_FILE_LINK" />
<!-- SVG tag (shows fallback icon - full SVG rendering coming soon) -->
<lib-icon icon="<svg>...</svg>" [iconType]="IconTypeEnum.DIRECT_TAG_SVG" />LazySelectComponent
Single-select dropdown with lazy loading, search, and scroll pagination.
- Selector:
lib-lazy-select - Extends:
BaseFormControl<string | null> - Supports: Template-driven, reactive forms, signal forms
@Component({
imports: [LazySelectComponent],
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)" /> `,
})
export class MyComponent {
readonly selectedId = signal<string | null>(null);
readonly items = signal<IDropDown[]>([]);
readonly loading = signal(false);
readonly total = signal<number | undefined>(undefined);
readonly pagination = signal<IPagination>({ currentPage: 0, pageSize: 20 });
}Inputs:
| Input | Type | Description |
| ---------------- | --------------------- | --------------------------------------------- |
| selectDataList | Array<IDropDown> | Dropdown options (required) |
| optionLabel | string | Label field name (required) |
| optionValue | string | Value field name (required) |
| isEditMode | boolean | Enable/disable editing (required) |
| isLoading | boolean | Loading state (required) |
| total | number \| undefined | Total items for pagination |
| pagination | IPagination | Current pagination state |
| placeHolder | string | Placeholder text (default: 'Select Option') |
Model: value - Two-way bound selected value (string | null)
Outputs: onSearch (debounced 500ms), onPagination (scroll-triggered)
LazyMultiSelectComponent
Multi-select dropdown with lazy loading, search, select-all, and scroll pagination.
- Selector:
lib-lazy-multi-select - Extends:
BaseFormControl<string[] | null> - Supports: Template-driven, reactive forms, signal forms
@Component({
imports: [LazyMultiSelectComponent],
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)" /> `,
})
export class MyComponent {
readonly selectedIds = signal<string[] | null>(null);
}Inputs: selectDataList, isEditMode, isLoading, total, pagination, placeHolder (same as LazySelectComponent). Does not have optionLabel/optionValue (uses IDropDown.label/IDropDown.value directly).
Model: value - Two-way bound selected values (string[] | null)
Computed signals: selectedValueDisplay (display text), isSelectAll (all items selected)
UserSelectComponent
Single user selection with lazy loading. Uses USER_PROVIDER internally or accepts custom loadUsers function.
- Selector:
lib-user-select - Supports: Template-driven, reactive forms, signal forms
@Component({
imports: [UserSelectComponent],
template: `
<!-- Simple usage - uses USER_PROVIDER internally -->
<lib-user-select [(value)]="selectedUserId" [isEditMode]="true" />
<!-- With custom loadUsers function -->
<lib-user-select [(value)]="selectedUserId" [isEditMode]="true" [loadUsers]="customLoadUsers" />
`,
})
export class MyComponent {
readonly selectedUserId = signal<string | null>(null);
}Inputs:
| Input | Type | Default | Description |
| ------------------- | ------------------------- | --------------- | ---------------------------- |
| isEditMode | boolean | required | Enable/disable editing |
| placeHolder | string | 'Select User' | Placeholder text |
| filterActive | boolean | true | Filter active users only |
| additionalFilters | Record<string, unknown> | {} | Extra filter params |
| pageSize | number | 20 | Page size for pagination |
| loadUsers | LoadUsersFn | - | Custom user loading function |
Model: value - Two-way bound selected user ID (string | null)
Outputs: userSelected (IUserBasicInfo | null), onError (Error)
UserMultiSelectComponent
Multiple user selection with lazy loading. Uses USER_PROVIDER internally or accepts custom loadUsers function.
- Selector:
lib-user-multi-select - Supports: Template-driven, reactive forms, signal forms
@Component({
imports: [UserMultiSelectComponent],
template: ` <lib-user-multi-select [(value)]="selectedUserIds" [isEditMode]="true" /> `,
})
export class MyComponent {
readonly selectedUserIds = signal<string[] | null>(null);
}Inputs: Same as UserSelectComponent.
Model: value - Two-way bound selected user IDs (string[] | null)
Outputs: usersSelected (IUserBasicInfo[]), onError (Error)
FileUploaderComponent
Drag & drop file upload with type filtering. Pass your own uploadFile function.
- Selector:
lib-file-uploader
@Component({
imports: [FileUploaderComponent],
template: `
<!-- Single image upload -->
<lib-file-uploader [uploadFile]="uploadFile" [acceptTypes]="['image/*']" [multiple]="false" (fileUploaded)="onFileUploaded($event)" />
<!-- Multiple document upload -->
<lib-file-uploader [uploadFile]="uploadFile" [acceptTypes]="FILE_TYPE_FILTERS.DOCUMENTS" [multiple]="true" [maxFiles]="5" (filesUploaded)="onFilesUploaded($event)" />
`,
})
export class MyComponent {
readonly uploadService = inject(UploadService);
readonly uploadFile: UploadFileFn = (file, options) => this.uploadService.uploadSingleFile(file, options);
}Inputs:
| Input | Type | Default | Description |
| --------------- | -------------------- | -------- | ------------------------------- |
| uploadFile | UploadFileFn | required | Upload function |
| acceptTypes | string[] | [] | Allowed MIME types |
| multiple | boolean | false | Allow multiple files |
| maxFiles | number | 10 | Max files for multiple |
| maxSizeMb | number | 10 | Max file size in MB |
| uploadOptions | IFileUploadOptions | {} | Upload options |
| disabled | boolean | false | Disable uploader |
| showPreview | boolean | true | Show selected files preview |
| autoUpload | boolean | true | Upload immediately on selection |
Outputs: fileUploaded (IUploadedFile), filesUploaded (IUploadedFile[]), onError (Error), fileSelected (File[])
FileSelectorDialogComponent
Dialog to browse and select existing files with filtering.
- Selector:
lib-file-selector-dialog
@Component({
imports: [FileSelectorDialogComponent],
template: ` <lib-file-selector-dialog [(visible)]="showFileSelector" [loadFiles]="loadFiles" [acceptTypes]="['image/*']" [multiple]="false" (fileSelected)="onFileSelected($event)" /> `,
})
export class MyComponent {
readonly showFileSelector = signal(false);
readonly fileService = inject(FileManagerApiService);
readonly loadFiles: LoadFilesFn = (filter) =>
this.fileService.getAll(filter.search, {
pagination: { currentPage: filter.page, pageSize: filter.pageSize },
});
}Inputs:
| Input | Type | Default | Description |
| -------------- | ------------- | --------------- | ------------------------ |
| loadFiles | LoadFilesFn | required | File loading function |
| header | string | 'Select File' | Dialog header |
| acceptTypes | string[] | [] | Allowed MIME types |
| multiple | boolean | false | Allow multiple selection |
| maxSelection | number | 10 | Max files for multiple |
| pageSize | number | 20 | Page size for pagination |
Model: visible - Two-way bound dialog visibility (boolean)
Outputs: fileSelected (IFileBasicInfo), filesSelected (IFileBasicInfo[]), closed (void), onError (Error)
6. Directives
HasPermissionDirective
Structural directive for permission-based rendering. Fail-closed: hides content when permissions not loaded.
- Selector:
[hasPermission] - Input:
hasPermission-string | ILogicNode | null
import { HasPermissionDirective, ILogicNode } from "@flusys/ng-shared";
@Component({
imports: [HasPermissionDirective],
template: `
<!-- Simple permission check -->
<button *hasPermission="'user.create'">Create User</button>
<!-- Complex AND/OR logic -->
<div *hasPermission="editLogic">Edit Panel</div>
`,
})
export class MyComponent {
readonly editLogic: ILogicNode = {
type: "group",
operator: "AND",
children: [
{ type: "action", actionId: "user.view" },
{ type: "action", actionId: "user.update" },
],
};
}EditModeElementChangerDirective
Toggles readonly/disabled state for form controls based on edit mode. Supports <input>, <p-select>, and <p-calendar>.
- Selector:
[appEditModeElementChanger] - Input:
isEditMode(required boolean)
<input [appEditModeElementChanger] [isEditMode]="isEditing()" /> <p-select [appEditModeElementChanger] [isEditMode]="isEditing()" />IsEmptyImageDirective
Automatically replaces broken or empty image src with a default fallback image.
- Selector:
img(applies to all<img>elements) - Input:
src(standard img src) - Default image:
lib/assets/images/default/default-image.jpg
<img [src]="imageUrl()" alt="Product" />
<!-- Falls back to default image on error or empty src -->PreventDefaultDirective
Prevents default browser behavior on specified events and emits the event.
- Selector:
[appPreventDefault] - Inputs:
eventType('click' | 'keydown' | 'keyup', default:'click'),preventKey(optional key filter) - Output:
action- Emits the prevented event
<a href="#" appPreventDefault (action)="handleClick($event)">Click me</a> <input appPreventDefault eventType="keydown" preventKey="Enter" (action)="onEnter($event)" />7. Guards
Route-level guards for permission-based access control. All guards deny access when permissions are not loaded (fail-closed).
permissionGuard
Single permission or complex logic check.
import { permissionGuard } from "@flusys/ng-shared";
const routes: Routes = [
// Simple permission
{ path: "users", canActivate: [permissionGuard("user.view")] },
// Complex logic (ILogicNode)
{
path: "admin",
canActivate: [
permissionGuard({
type: "group",
operator: "AND",
children: [
{ type: "action", actionId: "admin.view" },
{ type: "action", actionId: "admin.manage" },
],
}),
],
},
// Custom redirect on deny
{ path: "settings", canActivate: [permissionGuard("settings.view", "/access-denied")] },
];anyPermissionGuard
OR logic - allows access if user has ANY of the specified permissions.
{ path: 'reports', canActivate: [anyPermissionGuard(['report.view', 'report.export'])] }allPermissionsGuard
AND logic - allows access only if user has ALL specified permissions.
{ path: 'admin', canActivate: [allPermissionsGuard(['admin.view', 'admin.manage'])] }8. Utilities
Permission Evaluator
Pure functions for permission logic evaluation. Used internally by HasPermissionDirective and guards. Supports wildcard permissions.
import { evaluatePermission, evaluateLogicNode, hasAnyPermission, hasAllPermissions, hasPermission } from "@flusys/ng-shared";
const userPermissions = ["user.view", "user.create", "admin.*"];
// Low-level hasPermission check with wildcard support
hasPermission("user.view", userPermissions); // true (exact match)
hasPermission("admin.manage", userPermissions); // true (matches 'admin.*')
hasPermission("settings.view", ["*"]); // true (global wildcard)
// Evaluate string or ILogicNode
evaluatePermission("user.view", userPermissions); // true
evaluatePermission(null, userPermissions); // false
// Evaluate ILogicNode tree recursively
evaluateLogicNode(logicNode, userPermissions);
// Simple OR/AND checks (also support wildcards)
hasAnyPermission(["user.view", "user.delete"], userPermissions); // true (has user.view)
hasAllPermissions(["user.view", "user.delete"], userPermissions); // false (missing user.delete)Wildcard Rules:
| Pattern | Matches |
| ----------- | --------------------------------------- |
| * | All permissions (global wildcard) |
| module.* | All permissions starting with module. |
| user.read | Exact match only |
Implementation Details:
export function hasPermission(requiredPermission: string, userPermissions: string[]): boolean {
// Exact match
if (userPermissions.includes(requiredPermission)) return true;
// Wildcard matching
for (const permission of userPermissions) {
// Global wildcard
if (permission === "*") return true;
// Module wildcard (e.g., 'user.*' matches 'user.read')
if (permission.endsWith(".*")) {
const prefix = permission.slice(0, -1); // 'user.'
if (requiredPermission.startsWith(prefix)) return true;
}
}
return false;
}Scroll Pagination
Utility for lazy-loading dropdowns with scroll detection.
import { checkScrollPagination, ScrollPaginationConfig } from '@flusys/ng-shared';
// In a component
onScroll(event: Event): void {
const nextPagination = checkScrollPagination(event, {
pagination: this.pagination(),
total: this.total(),
isLoading: this.isLoading(),
threshold: 50, // pixels from bottom (default: 50)
});
if (nextPagination) {
this.onPagination.emit(nextPagination);
}
}Interface: ScrollPaginationConfig - { threshold?, pagination, total, isLoading }
Returns: IPagination | null - Next page pagination or null if not needed.
9. Classes
BaseFormControl
Abstract base class for custom form controls. Implements both ControlValueAccessor (reactive forms) and FormValueControl (signal forms).
import { BaseFormControl, provideValueAccessor } from "@flusys/ng-shared";
@Component({
selector: "my-select",
providers: [provideValueAccessor(MySelectComponent)],
})
export class MySelectComponent extends BaseFormControl<string | null> {
override readonly value = model<string | null>(null);
constructor() {
super();
this.initializeFormControl(); // Required for ControlValueAccessor sync
}
}
// Usage in all form patterns:
// Template-driven: <my-select [(value)]="selectedId" />
// Reactive forms: <my-select [formControl]="myControl" />
// Signal forms: <my-select [formField]="formTree.myField" />Abstract property: value: ModelSignal<T> - Must override with model<T>()
Models: disabled (boolean), touched (boolean)
Methods: initializeFormControl() (call in constructor), markAsTouched() (call on blur)
Helper: provideValueAccessor(ComponentClass) - Factory for NG_VALUE_ACCESSOR provider
BaseFormPage
Abstract directive for form page components (create/edit).
import { BaseFormPage } from '@flusys/ng-shared';
@Component({ ... })
export class ProductFormComponent extends BaseFormPage<IProduct, IProductFormModel> {
private readonly productService = inject(ProductApiService);
private readonly _formModel = signal<IProductFormModel>({ name: '', price: 0 });
readonly formModel = this._formModel.asReadonly();
getFormModel(): Signal<IProductFormModel> { return this.formModel; }
getResourceRoute(): string { return '/products'; }
getResourceName(): string { return 'Product'; }
isFormValid(): boolean { return this.formModel().name.trim().length > 0; }
loadItem(id: string): void {
this.productService.findById(id).subscribe(res => {
if (res.success && res.data) {
this.existingItem.set(res.data);
this._formModel.set({ name: res.data.name, price: res.data.price });
}
});
}
createItem(model: IProductFormModel): Observable<unknown> {
return this.productService.insert(model);
}
updateItem(model: IProductFormModel): Observable<unknown> {
return this.productService.update({ id: this.existingItem()!.id, ...model });
}
}Signals: isLoading, existingItem, isEditMode (computed)
Methods: onSubmit(), onCancel(), showSuccess(), showError(), showValidationError()
BaseListPage
Abstract directive for list page components with pagination and CRUD operations.
import { BaseListPage } from '@flusys/ng-shared';
@Component({ ... })
export class UserListComponent extends BaseListPage<IUser> {
private readonly userService = inject(UserApiService);
getResourceRoute(): string { return '/users'; }
getDeleteConfirmMessage(user: IUser): string { return `Delete "${user.name}"?`; }
async loadData(): Promise<void> {
this.isLoading.set(true);
const res = await this.userService.findByIdAsync(...);
this.items.set(res.data ?? []);
this.total.set(res.meta?.total ?? 0);
this.isLoading.set(false);
}
}Signals: items, isLoading, total, pageSize, first, currentPage (computed), showCompanyInfo (computed)
Methods: onCreate(), onEdit(id), onPageChange(event), onDelete(), onDeleteAsync(), showSuccess(), showError(), showInfo(), showWarn()
10. Modules
AngularModule
Re-exports common Angular modules for convenience.
import { AngularModule } from "@flusys/ng-shared";
// Includes: CommonModule, FormsModule, ReactiveFormsModule, RouterLink, RouterOutlet,
// RouterLinkActive, NgOptimizedImage, NgComponentOutlet, + directives (IsEmptyImageDirective, PreventDefaultDirective)
// Providers: DatePipePrimeModule
Re-exports PrimeNG component modules for convenience.
import { PrimeModule } from "@flusys/ng-shared";
// Includes 30 modules:
// - Layout: AccordionModule, CardModule, DividerModule, PanelModule, SplitterModule, TabsModule, ToolbarModule
// - Form: AutoCompleteModule, CheckboxModule, DatePickerModule, InputTextModule, InputTextareaModule,
// MultiSelectModule, RadioButtonModule, SelectModule, ToggleSwitchModule
// - Button: ButtonModule, SpeedDialModule, SplitButtonModule
// - Data: PaginatorModule, TableModule, TreeTableModule
// - Overlay: DialogModule, DrawerModule, PopoverModule, TooltipModule
// - File: FileUploadModule
// - Media: ImageModule
// - Misc: BadgeModule, TagModule11. Provider Interfaces (Package Independence)
ng-shared defines provider interfaces to enable feature packages (ng-iam, ng-storage) to access auth functionality without direct dependencies.
Architecture
ng-shared (defines interfaces + tokens)
|
ng-auth (implements interfaces with adapters)
|
ng-iam/ng-storage (consume interfaces via DI)Available Providers
IUserProvider / USER_PROVIDER
User list access for IAM user selection.
interface IUserBasicInfo {
id: string;
name: string;
email: string;
}
interface IUserProvider {
getUsers(filter?: { page?: number; pageSize?: number; search?: string; companyId?: string; branchId?: string }): Observable<IListResponse<IUserBasicInfo>>;
}Token Error Message: 'USER_PROVIDER not configured. Please provide an implementation in app.config.ts'
ICompanyApiProvider / COMPANY_API_PROVIDER
Company list access for IAM company selection.
interface ICompanyBasicInfo {
id: string;
name: string;
slug?: string;
}
interface ICompanyApiProvider {
getCompanies(filter?: { page?: number; pageSize?: number; search?: string }): Observable<IListResponse<ICompanyBasicInfo>>;
}Token Error Message: 'COMPANY_API_PROVIDER not configured. Please provide an implementation in app.config.ts'
IUserPermissionProvider / USER_PERMISSION_PROVIDER
User permission queries for IAM.
interface IUserBranchPermission {
branchId: string;
branchName: string;
permissions: string[];
}
interface IUserPermissionProvider {
getUserBranchPermissions(userId: string): Observable<ISingleResponse<IUserBranchPermission[]>>;
}Token Error Message: 'USER_PERMISSION_PROVIDER not configured. Please provide an implementation in app.config.ts'
IFileProvider / FILE_PROVIDER
File operations provider for file selection and upload. Implemented by ng-storage. Optional token - use with inject(..., { optional: true }).
interface IFileProvider {
loadFiles(filter: IFileSelectFilter): Observable<IListResponse<IFileBasicInfo>>;
uploadFile(file: File, options?: IFileUploadOptions): Observable<ISingleResponse<IUploadedFile>>;
uploadMultipleFiles?(files: File[], options?: IFileUploadOptions): Observable<ISingleResponse<IUploadedFile[]>>;
getFileUrls?(fileIds: string[]): Observable<ISingleResponse<IFileBasicInfo[]>>;
loadFolders?(filter: ISelectFilter): Observable<IListResponse<IFolderBasicInfo>>;
loadStorageConfigs?(filter: ISelectFilter): Observable<IListResponse<IStorageConfigBasicInfo>>;
}IProfilePermissionProvider / PROFILE_PERMISSION_PROVIDER
User permission queries for ng-auth profile page. Implemented by ng-iam. Optional token - use with inject(..., { optional: true }).
interface IProfileRoleInfo {
id: string;
name: string;
description?: string | null;
}
interface IProfileActionInfo {
id: string;
code: string;
name: string;
description?: string | null;
}
interface IProfilePermissionProvider {
getUserRoles(userId: string, branchId?: string): Observable<ISingleResponse<IProfileRoleInfo[]>>;
getUserActions(userId: string, branchId?: string): Observable<ISingleResponse<IProfileActionInfo[]>>;
}IAuthStateProvider / AUTH_STATE_PROVIDER
Auth state access for feature packages (form-builder, etc.) that need to check authentication without depending on ng-auth directly.
interface IAuthStateProvider {
/** Signal indicating if user is currently authenticated */
isAuthenticated: Signal<boolean>;
/** Initialize auth state (restore session from cookies/storage) */
initialize(): Observable<void>;
}Token Error Message: 'AUTH_STATE_PROVIDER not configured. Please provide an implementation in app.config.ts'
Usage:
import { AUTH_STATE_PROVIDER } from '@flusys/ng-shared';
@Component({...})
export class PublicFormComponent {
private readonly authState = inject(AUTH_STATE_PROVIDER);
ngOnInit() {
this.authState.initialize().subscribe(() => {
if (this.authState.isAuthenticated()) {
// User is logged in
}
});
}
}IUserListProvider / USER_LIST_PROVIDER
Extends user list pages with extra columns, actions, and data enrichment. Optional token - use with inject(..., { optional: true }).
interface IUserListItem {
id: string;
name: string;
email: string;
phone?: string;
isActive?: boolean;
}
interface IUserListAction<T = IUserListItem> {
id: string;
label: string;
icon?: string;
severity?: "primary" | "secondary" | "success" | "info" | "warn" | "danger";
permission?: string;
tooltip?: string;
disabled?: boolean | ((user: T) => boolean);
visible?: boolean | ((user: T) => boolean);
}
interface IUserListColumn {
field: string;
header: string;
width?: string;
sortable?: boolean;
templateType?: "text" | "badge" | "date" | "boolean" | "custom";
}
interface IUserListFilter {
page?: number;
pageSize?: number;
search?: string;
isActive?: boolean;
companyId?: string;
branchId?: string;
}
interface IUserListProvider<T extends IUserListItem = IUserListItem> {
getExtraColumns?(): IUserListColumn[];
getExtraRowActions?(): IUserListAction<T>[];
getExtraToolbarActions?(): IUserListAction<T>[];
onRowAction?(actionId: string, user: T): void;
onToolbarAction?(actionId: string, selectedUsers: T[]): void;
enrichListData?(users: T[]): Observable<T[]>;
}Usage:
// In app.config.ts
providers: [{ provide: USER_LIST_PROVIDER, useClass: MyUserListProvider }];
// Implementation
@Injectable({ providedIn: "root" })
export class MyUserListProvider implements IUserListProvider {
getExtraRowActions() {
return [{ id: "assign-role", label: "Assign Role", icon: "pi pi-users" }];
}
}Usage in Consuming Packages
import { USER_PROVIDER } from "@flusys/ng-shared";
export class UserSelectorComponent {
private readonly userProvider = inject(USER_PROVIDER);
loadUsers() {
this.userProvider.getUsers({ page: 0, pageSize: 50 }).subscribe((response) => {
this.users.set(response.data ?? []);
});
}
}Wiring in App
// app.config.ts
import { provideAuthProviders } from "@flusys/ng-auth";
export const appConfig: ApplicationConfig = {
providers: [
...provideAuthProviders(), // Registers all auth adapters for provider tokens
],
};See Also
- AUTH-GUIDE.md - Adapter implementations
- IAM-GUIDE.md - IAM usage examples
- ../CLAUDE.md - Complete pattern documentation
Best Practices
API Services
- Extend
ApiResourceServicefor new services (signal-based) - Use reactive signals (
data,isLoading,total) in templates - Use
fetchList()to trigger queries (also initializes resource),reload()to refresh - Use async methods (
insertAsync,updateAsync) for one-off operations - Resource is lazy-initialized - no HTTP requests until first
fetchList()orinitListResource()
File URLs
- Always use
FileUrlService- never construct URLs manually - Use
fetchSingleFileUrl()for one-off fetches,fetchFileUrls()for batches - Use
fileUrlSignal()for reactive template bindings
Components
- Use
AngularModuleandPrimeModulefor common imports - Use signal inputs via
input()andinput.required() - Components use
lib-prefix for selectors
Directives
- Directives use
appprefix for selectors (appPreventDefault,appEditModeElementChanger) - Exception:
HasPermissionDirectiveuses[hasPermission] - Exception:
IsEmptyImageDirectiveusesimgselector (auto-applies to all images)
Permissions
- Use
HasPermissionDirectivefor template-level permission checks - Use permission guards for route-level access control
- Use
PermissionValidatorServicefor programmatic checks in services - Permissions follow fail-closed model: no access by default
- Wildcards supported:
*(all),module.*(module-scoped)
Common Issues
ApiResourceService Not Updating UI
Problem: UI doesn't update after operations.
Solution: Use signal syntax in templates:
<!-- Correct -->
<div>{{ users() }}</div>
<!-- Wrong -->
<div>{{ users }}</div>FileUrlService Returns Error
Problem: File URL fetching fails.
Solution:
- Ensure storage service is enabled in environment config
- Verify file ID exists in database
- Check storage provider configuration (local/S3/Azure)
Circular Dependency with ng-layout
Problem: Build fails with circular dependency.
Solution: ng-shared must NEVER import from ng-layout. Move shared components to ng-shared, layout-specific components to ng-layout.
Provider Token Errors
Problem: 'XXX_PROVIDER not configured' error at runtime.
Solution: Ensure the provider is registered in app.config.ts:
// Required providers
providers: [
{ provide: USER_PROVIDER, useClass: AuthUserProvider },
{ provide: COMPANY_API_PROVIDER, useClass: AuthCompanyApiProvider },
{ provide: USER_PERMISSION_PROVIDER, useClass: AuthUserPermissionProvider },
{ provide: AUTH_STATE_PROVIDER, useClass: AuthStateProviderAdapter },
];
// OR use the convenience function
providers: [...provideAuthProviders()];Permissions Not Working
Problem: hasPermission() returns false even though user should have access.
Solution:
- Check if permissions are loaded:
permissionValidator.isLoaded() - Verify permission codes match exactly (case-sensitive)
- For wildcard access, ensure user has
*ormodule.*in their permissions
API Reference
Services
| Service | Description |
| ---------------------------- | ---------------------------------------------------------------------------------------- |
| ApiResourceService<DTO, T> | Signal-based CRUD with resource() API (lazy-initialized, accepts optional serviceName) |
| FileUrlService | Cloud storage URL fetching |
| PermissionValidatorService | Permission state management with wildcards |
| CookieService | SSR-aware cookie reading |
| PlatformService | SSR environment detection |
Classes
| Class | Description |
| -------------------- | -------------------------------------------------- |
| ApiResourceService | Signal-based CRUD base class (alias: ApiService) |
| BaseFormControl | Abstract base for custom form controls |
| BaseFormPage | Abstract directive for create/edit pages |
| BaseListPage | Abstract directive for list pages |
Constants
| Constant | Description |
| ------------------- | ----------------------------------------------------- |
| PERMISSIONS | Aggregated permission codes by module |
| USER_PERMISSIONS | { CREATE, READ, UPDATE, DELETE } for users |
| ROLE_PERMISSIONS | { CREATE, READ, UPDATE, DELETE } for roles |
| FILE_PERMISSIONS | { CREATE, READ, UPDATE, DELETE } for files |
| FILE_TYPE_FILTERS | Predefined MIME type arrays (IMAGES, DOCUMENTS, etc.) |
Components
| Component | Selector | Description |
| ----------------------------- | -------------------------- | ------------------------- |
| IconComponent | lib-icon | Flexible icon renderer |
| LazySelectComponent | lib-lazy-select | Lazy-loading dropdown |
| LazyMultiSelectComponent | lib-lazy-multi-select | Lazy-loading multi-select |
| UserSelectComponent | lib-user-select | Single user selector |
| UserMultiSelectComponent | lib-user-multi-select | Multiple user selector |
| FileUploaderComponent | lib-file-uploader | Drag & drop file upload |
| FileSelectorDialogComponent | lib-file-selector-dialog | File browser dialog |
Directives
| Directive | Selector | Description |
| --------------------------------- | ----------------------------- | --------------------------------- |
| HasPermissionDirective | [hasPermission] | Permission-based rendering |
| EditModeElementChangerDirective | [appEditModeElementChanger] | Toggle edit mode on form controls |
| IsEmptyImageDirective | img | Image fallback on error/empty |
| PreventDefaultDirective | [appPreventDefault] | Prevent default event behavior |
Guards
| Guard | Description |
| --------------------- | ------------------------------------- |
| permissionGuard | Single permission or ILogicNode check |
| anyPermissionGuard | OR logic (any of listed permissions) |
| allPermissionsGuard | AND logic (all of listed permissions) |
Interfaces
| Interface | Description |
| ----------------------- | ------------------------------------------------------------------------------ |
| IBaseEntity | Base entity with ID and timestamps |
| ILoggedUserInfo | Current user info with company ctx |
| IFilterData | Filter, pagination, sort payload |
| IFilter | Filter object (supports arrays) |
| IDeleteData | Delete request payload |
| IDropDown | Simple label/value pair |
| ISingleResponse<T> | Single item response |
| IListResponse<T> | List with pagination |
| IBulkResponse<T> | Bulk operation response |
| IMessageResponse | Message-only response |
| IErrorResponse | Error with validation details |
| ILogicNode | Permission logic tree (AND/OR nodes) |
| IUserSelectFilter | User select filter params |
| LoadUsersFn | User loading function type |
| IFileBasicInfo | Basic file info for selectors |
| IFolderBasicInfo | Folder info for selectors |
| IStorageConfigBasicInfo | Storage config info for selectors |
| IFileUploadOptions | Upload options (compression, etc.) |
| IUploadedFile | Uploaded file response |
| IFileSelectFilter | File select filter params |
| ISelectFilter | Filter params for folder/config selectors |
| LoadFilesFn | File loading function type |
| UploadFileFn | Single file upload function type |
| UploadMultipleFilesFn | Multiple files upload function type |
| IFileProvider | File operations provider interface |
| FilesResponseDto | File URL service response |
| IAuthStateProvider | Auth state provider interface |
| IUserListProvider | User list extensions provider |
| IUserListItem | Base user for list operations |
| IUserListAction | User list action definition |
| IUserListColumn | Extra column for user list |
| IUserListFilter | User list filter parameters |
| IUserBranchPermission | User permissions per branch |
| ServiceName | 'auth' \| 'administration' \| 'iam' \| 'storage' \| 'formBuilder' \| 'email' |
Injection Tokens
| Token | Interface | Optional | Description |
| ----------------------------- | ---------------------------- | -------- | ------------------------------------- |
| USER_PROVIDER | IUserProvider | No | User list for IAM |
| COMPANY_API_PROVIDER | ICompanyApiProvider | No | Company list for IAM |
| USER_PERMISSION_PROVIDER | IUserPermissionProvider | No | User permission queries |
| AUTH_STATE_PROVIDER | IAuthStateProvider | No | Auth state for feature packages |
| FILE_PROVIDER | IFileProvider | Yes | File operations (ng-storage) |
| PROFILE_PERMISSION_PROVIDER | IProfilePermissionProvider | Yes | User permissions for profile (ng-iam) |
| USER_LIST_PROVIDER | IUserListProvider | Yes | User list extensions |
10. Translation & Localization
Overview
The ng-shared package provides optional localization support via two modes:
- Fallback-Only Mode - Hardcoded fallback messages (no API)
- Full API Mode - Dynamic translations from API with fallback safety net
Fallback-Only Mode
Use when you want simple hardcoded translations without a localization system:
// app.config.ts
import { provideFallbackLocalization } from '@flusys/ng-shared';
export const appConfig: ApplicationConfig = {
providers: [
// ... other providers
...provideFallbackLocalization(), // ← One-liner setup
],
};What it provides:
FALLBACK_MESSAGES_REGISTRY- Token for storing hardcoded messagesTRANSLATE_ADAPTER- Fallback implementation that reads from registry- Works with route resolvers that register fallback messages
Message flow:
Route with resolveTranslationModule({
modules: ['email'],
fallbackMessages: EMAIL_MESSAGES
})
↓
Resolver registers in FALLBACK_MESSAGES_REGISTRY
↓
TRANSLATE_ADAPTER reads from registry
↓
Components/TranslatePipe render valuesFull API Mode
Use when you need dynamic translations, language switching, and management UI:
// app.config.ts
import { provideLocalization, getLocalizationConfig } from '@flusys/ng-localization';
export const appConfig: ApplicationConfig = {
providers: [
// ... other providers
...provideLocalization(
getLocalizationConfig({
defaultLanguageCode: 'en',
enableLayoutSelector: true, // Show language switcher
})
),
],
};What it provides:
LocalizationStateService- Manages current language and translationsLocalizationApiService- Fetches translations from backendTranslateService- Registered asTRANSLATE_ADAPTER- Language selector component (if
enableLayoutSelector: true) - Management pages for languages and translations
Route Resolver Setup
All routes should use resolveTranslationModule with fallback messages:
// email.routes.ts
import { resolveTranslationModule, EMAIL_MESSAGES, SHARED_MESSAGES } from '@flusys/ng-shared';
export const EMAIL_ROUTES: Routes = [
{
path: '',
resolve: {
translations: resolveTranslationModule({
modules: ['email'],
fallbackMessages: { ...EMAIL_MESSAGES, ...SHARED_MESSAGES }
})
},
// ... route definition
}
];TranslatePipe Usage
Works identically in both modes:
// Template
<h1>{{ 'email.title' | translate }}</h1>
<p>{{ 'email.message' | translate: { count: itemCount } }}</p>
// Component
const title = this.translateAdapter.translate('email.title');Configuration (Full API Mode Only)
interface ILocalizationConfig {
defaultLanguageCode: string; // Required: 'en', 'ar', etc.
loadStrategy?: 'all' | 'modules'; // Default: 'modules'
initialModules?: string[]; // Default: []
enableLayoutSelector?: boolean; // Default: false
}Key Files
| File | Purpose |
|------|---------|
| providers/fallback-localization.providers.ts | Fallback mode setup |
| resolvers/translation-module.resolver.ts | Route resolver for both modes |
| pipes/translate.pipe.ts | Translation pipe |
When to Use Each Mode
Use Fallback-Only:
- Simple applications with static content
- No language switching needed
- No translation management UI
- Lightweight setup
Use Full API:
- Multi-language support required
- User can switch languages
- Admin manages translations
- Real-time translation updates
- Translation management UI needed
See Also
- CORE-GUIDE.md - Configuration, interceptors
- LAYOUT-GUIDE.md - Layout system
- AUTH-GUIDE.md - Auth adapters for provider interfaces
- IAM-GUIDE.md - IAM permission usage
Last Updated: 2026-03-01 Version: 3.0.1 Angular Version: 21
