npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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 serviceName is provided, uses getServiceUrl(config, serviceName) to resolve the base URL
  • Falls back to APP_CONFIG.apiBaseUrl if service not found or serviceName not 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 initialization

Internal 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: ApiService is exported as an alias for ApiResourceService for 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 (optional IconTypeEnum, 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: DatePipe

PrimeModule

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, TagModule

11. 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


Best Practices

API Services

  • Extend ApiResourceService for 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() or initListResource()

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 AngularModule and PrimeModule for common imports
  • Use signal inputs via input() and input.required()
  • Components use lib- prefix for selectors

Directives

  • Directives use app prefix for selectors (appPreventDefault, appEditModeElementChanger)
  • Exception: HasPermissionDirective uses [hasPermission]
  • Exception: IsEmptyImageDirective uses img selector (auto-applies to all images)

Permissions

  • Use HasPermissionDirective for template-level permission checks
  • Use permission guards for route-level access control
  • Use PermissionValidatorService for 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:

  1. Ensure storage service is enabled in environment config
  2. Verify file ID exists in database
  3. 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:

  1. Check if permissions are loaded: permissionValidator.isLoaded()
  2. Verify permission codes match exactly (case-sensitive)
  3. For wildcard access, ensure user has * or module.* 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:

  1. Fallback-Only Mode - Hardcoded fallback messages (no API)
  2. 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 messages
  • TRANSLATE_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 values

Full 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 translations
  • LocalizationApiService - Fetches translations from backend
  • TranslateService - Registered as TRANSLATE_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


Last Updated: 2026-03-01 Version: 3.0.1 Angular Version: 21