@flusys/ng-shared
v5.2.0
Published
Shared components and utilities for FLUSYS Angular packages
Readme
@flusys/ng-shared
Shared utilities, provider tokens, base page classes, and reusable components for the FLUSYS Angular platform.
Installation
npm install @flusys/ng-shared @flusys/ng-core1. ApiResourceService — Signal-Driven CRUD
Extend this class to get a reactive list resource backed by resource(), with pagination, search, and all standard POST-only RPC endpoints.
import { Injectable, inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { ApiResourceService } from "@flusys/ng-shared";
export interface IProduct {
id: string;
name: string;
price: number;
}
export interface IProductDto {
name: string;
price: number;
}
@Injectable({ providedIn: "root" })
export class ProductService extends ApiResourceService<IProductDto, IProduct> {
constructor() {
super("products", "administration"); // moduleApiName, http, serviceName (key in APP_CONFIG.services)
}
}Using the reactive list in a component:
@Component({ ... })
export class ProductListComponent {
private readonly svc = inject(ProductService);
readonly items = this.svc.data; // Signal<IProduct[]>
readonly total = this.svc.total; // Signal<number>
readonly loading = this.svc.isLoading; // Signal<boolean>
ngOnInit() { this.svc.fetchList(); }
onSearch(q: string) { this.svc.fetchList(q); }
onPageChange(page: number, size: number) {
this.svc.setPagination({ currentPage: page, pageSize: size });
}
async onCreate(dto: IProductDto) {
await this.svc.insert(dto);
this.svc.reload();
}
async onDelete(id: string) {
await this.svc.delete({ id });
this.svc.reload();
}
}Available CRUD methods: insert, insertMany, findById, findByIds, getAll, getByFilter, update, updateMany, bulkUpsert, delete.
2. FileUrlService — Presigned URL Fetching
Never construct file URLs manually. FileUrlService handles S3/Azure presigned URLs, caching, and deduplication.
import { FileUrlService } from '@flusys/ng-shared';
@Component({ ... })
export class AvatarComponent {
private readonly fileUrlService = inject(FileUrlService);
readonly avatarUrl = signal<string | null>(null);
ngOnInit() {
this.fileUrlService.fetchSingleFileUrl('file-id-123').subscribe(file => {
this.avatarUrl.set(file?.url ?? null);
});
}
// Batch fetch — only requests IDs not already cached
loadMultiple(ids: string[]) {
this.fileUrlService.fetchFileUrls(ids).subscribe(files => { /* ... */ });
}
// Synchronous read from cache after a prior fetch
getCachedUrl(id: string): string | null {
return this.fileUrlService.getFileUrl(id);
}
}3. PermissionValidatorService + HasPermissionDirective
const pv = inject(PermissionValidatorService);
pv.hasPermission("user.create"); // true
pv.hasPermission("report.export"); // true — wildcard match
pv.isPermissionsLoaded(); // boolean<!-- Removes element from DOM when permission fails -->
<button *hasPermission="'user.create'">Create User</button>4. Permission Guards
import { permissionGuard, anyPermissionGuard, allPermissionsGuard } from "@flusys/ng-shared";
export const routes: Routes = [
{
path: "users",
canActivate: [permissionGuard("user.view")], // single
loadComponent: () => import("./user-list.component"),
},
{
path: "reports",
canActivate: [anyPermissionGuard(["report.view", "report.export"])], // OR
loadComponent: () => import("./reports.component"),
},
{
path: "admin",
canActivate: [allPermissionsGuard(["admin.view", "admin.manage"])], // AND
loadComponent: () => import("./admin.component"),
},
];All three guards redirect to / by default. Pass a second argument to change it: permissionGuard('user.view', '/access-denied').
5. TranslatePipe
{{ 'shared.save' | translate }} {{ 'pagination.showing' | translate: { from: 1, to: 10 } }}License
MIT © FLUSYS
