Skip to content

Shared Libraries

The Nx workspace contains four shared libraries under libs/. They are consumed by every remote app as normal TypeScript imports — not through Module Federation's shared config. This page covers what each library contains, how to add code to them, and why the isolation-first bundling decision was made.


Library Summary

LibraryPathConsumersBundle Size Impact
shared-liblibs/shared-lib/All remote apps~200 KB gzipped per remote
microtec-auth-liblibs/microtec-auth-lib/All remote apps~30 KB gzipped per remote
apps-shared-liblibs/apps-shared-lib/ERP remotes only~50 KB gzipped per remote
shared-assetslibs/shared-assets/All apps (assets, not TypeScript)Loaded from CDN, not bundled

shared-lib

The core UI and utility library. Every Angular remote imports from shared-lib for its foundational building blocks.

Contents

CategoryExamples
UI Componentslib-table, lib-select, lib-dialog, lib-confirm, lib-loader, lib-page-header, lib-breadcrumbs, lib-microtec-viewer
LayoutSidebar component, header component, page shell wrapper
DirectivesappPermission (show/hide by role), appLoading, appConfirm
PipestranslateEnum, microtecDate, microtecCurrency, safeHtml
ValidatorsSaudi National ID, Arabic text pattern, password strength, email
ServicesLoaderService, StorageService, TranslationService, ToastService
GuardsAuthGuard, PermissionGuard (re-exported from microtec-auth-lib)
Base ClassesBaseListComponent, BaseFormComponent, BaseService
ModelsCommon DTOs, ApiResponse<T>, PaginatedResult<T>, LookupDto

File structure

libs/shared-lib/src/
├── lib/
│   ├── components/          # Reusable UI components
│   ├── directives/          # Angular directives
│   ├── pipes/               # Angular pipes
│   ├── services/            # Shared services
│   ├── guards/              # Route guards (wraps microtec-auth-lib)
│   ├── models/              # Common DTOs and interfaces
│   ├── validators/          # FluentValidation-style Angular validators
│   └── modules/
│       ├── layout/          # Sidebar, header, layout shell
│       └── shared.module.ts # SharedModule — import this in every feature module
└── index.ts                 # Public API barrel file

How to import

typescript
// In any remote app's feature module
import { SharedModule } from 'shared-lib';

@NgModule({
  imports: [SharedModule],
})
export class AccountingModule {}
typescript
// Direct named imports
import { BaseListComponent, ApiResponse, LookupDto } from 'shared-lib';

How to add a new component

  1. Create the component under libs/shared-lib/src/lib/components/<component-name>/.
  2. Declare and export it in SharedModule (libs/shared-lib/src/lib/modules/shared.module.ts).
  3. Export the component class from libs/shared-lib/src/index.ts.
  4. Test in isolation before using in any remote app.

Keep shared-lib generic

shared-lib must not contain any business-domain logic (accounting, HR, etc.). Domain-specific shared components belong in apps-shared-lib. If a component references a backend endpoint or a domain model, it is in the wrong library.


microtec-auth-lib

Wraps keycloak-angular and provides all authentication primitives. Remote apps never import keycloak-angular directly — they always go through microtec-auth-lib.

Contents

ExportDescription
AuthServiceLogin, logout, isAuthenticated(), getUserClaims(), hasRole()
AuthGuardCanActivate guard — redirects to Keycloak login if not authenticated
PermissionGuardCanActivate guard — checks a specific Keycloak role; returns 403 page if missing
KeycloakInterceptorHttpInterceptor — injects Authorization: Bearer <token> on every outbound request
KeycloakInitFactoryAPP_INITIALIZER factory — determines realm from URL path and initialises Keycloak
AUTH_CONFIG injection tokenRuntime configuration for Keycloak URL, realm, and client ID

Realm detection logic

KeycloakInitFactory reads the current URL path at app startup:

typescript
// libs/microtec-auth-lib/src/lib/keycloak-init.factory.ts
export function keycloakInitFactory(keycloak: KeycloakService, config: AuthConfig) {
  return async () => {
    const realm = window.location.pathname.startsWith('/bussiness-owners')
      ? 'businessowner'
      : config.realm; // 'microtec' for all ERP apps

    await keycloak.init({
      config: {
        url: config.keycloakUrl,
        realm,
        clientId: config.clientId,
      },
      initOptions: {
        onLoad: 'check-sso',
        silentCheckSsoRedirectUri: `${window.location.origin}/assets/silent-check-sso.html`,
      },
    });
  };
}

How to register in a remote app

typescript
// apps/apps-accounting/src/app/app.module.ts
import { MicrotecAuthModule } from 'microtec-auth-lib';
import { environment } from '../environments/environment';

@NgModule({
  imports: [
    MicrotecAuthModule.forRoot({
      keycloakUrl: environment.keycloakUrl,
      realm: environment.keycloakRealm,
      clientId: 'angular',
    }),
  ],
})
export class AppModule {}

How to use in services

typescript
import { AuthService } from 'microtec-auth-lib';

@Injectable({ providedIn: 'root' })
export class InvoiceService {
  constructor(private auth: AuthService, private http: HttpClient) {}

  canPostInvoice(): boolean {
    return this.auth.hasRole('accounting.invoice.post');
  }
}

apps-shared-lib

ERP-domain-specific shared code that is too specific for shared-lib but used across more than one remote app.

Contents

CategoryExamples
Module constantsMODULE_PERMISSIONS — permission key constants per module
Lookup componentsAccountLookupComponent, CostCenterLookupComponent, CustomerLookupComponent — tenant-aware typeahead fields
Report viewerReportViewerComponent — embeds the reporting service iframe
Import/Export UIImportButtonComponent, ExportButtonComponent, ImportDialogComponent
Shared ERP modelsTenantContext, ModuleActivation, PermissionSet
Shared ERP servicesLookupService, ModuleService, ReportService

How to import

typescript
import { AppsSharedModule } from 'apps-shared-lib';
import { MODULE_PERMISSIONS } from 'apps-shared-lib';

How to add a new lookup component

Lookup components are the most common addition. They wrap an API call for a specific domain entity and expose a PrimeNG Dropdown or AutoComplete:

  1. Create the component in libs/apps-shared-lib/src/lib/lookups/<entity>-lookup/.
  2. Declare and export it in AppsSharedModule.
  3. Export from libs/apps-shared-lib/src/index.ts.
  4. The component should accept an (ngModelChange) or [formControlName] binding.

shared-assets

Static assets shared across all apps. This library contains no TypeScript — only files copied into each app's dist/ folder at build time via the publishAssets script.

Contents

libs/shared-assets/
├── src/
│   └── assets/
│       ├── langs/
│       │   ├── shared/
│       │   │   ├── ar.json        # Arabic translations (shared keys)
│       │   │   └── en.json        # English translations (shared keys)
│       │   └── <module>/          # Module-specific translations
│       ├── icons/
│       │   └── sprite.svg         # Icon sprite
│       ├── images/
│       │   ├── logo.svg
│       │   └── logo-dark.svg
│       └── silent-check-sso.html  # Keycloak silent SSO redirect page

How shared-assets gets distributed

The publishAssets npm script runs after every build and copies the shared-assets contents into each app's output directory:

bash
npm run publishAssets
# Runs: node ./publish_assets.js

The script copies libs/shared-assets/src/assets/ into dist/<app>/assets/ for every built app. This means each deployed SWA contains the shared translations and images alongside its own app-specific assets.

How to add a new shared translation key

  1. Add the key to libs/shared-assets/src/assets/langs/shared/en.json.
  2. Add the Arabic equivalent to libs/shared-assets/src/assets/langs/shared/ar.json.
  3. Use the key with the shared_ prefix convention: shared_save, shared_cancel, shared_confirm_delete.
  4. Run publishAssets to propagate to dist folders.

Why Shared Libraries Are NOT in MF shared config

A common question is why these libraries are bundled into each remote rather than declared as shared singletons in the Module Federation configuration.

The answer is deployment independence:

The trade-off is bundle size: each remote includes its own copy of shared-lib (~200 KB gzipped). This is acceptable because:

  • Each SWA is deployed to Azure CDN with long-lived cache headers
  • Users only load each remote once per session
  • Deployment independence eliminates an entire class of production incidents

Angular core (@angular/core, rxjs) remains in the MF shared config as a true singleton because two Angular instances on one page break change detection fundamentally.

Internal Documentation — Microtec Platform Team