Appearance
Frontend Technology Reference
Detailed reference for every significant technology used in the Microtec ERP Angular micro-frontend platform.
Framework
Angular 17+
Version: 17.x (latest minor tracked)
Role: Primary UI framework for all 10 ERP web applications
Module system: NgModule-based (standalone: false) — no standalone components
Key features used:
- Lazy-loaded feature modules (consumed by Module Federation host)
HttpClientModulewith interceptors for auth and correlation IDs- Angular Router with child routes per micro-frontend
- Reactive Forms (
FormBuilder,FormGroup,FormControl) - Angular CDK — drag-and-drop, portal, overlay
typescript
// Standard feature module structure used across all micro-frontends
@NgModule({
declarations: [InvoiceListComponent, InvoiceFormComponent],
imports: [
CommonModule,
ReactiveFormsModule,
PrimeNGModule, // local barrel from shared-lib
NgRxModule, // StoreModule.forFeature, EffectsModule.forFeature
AccountingRoutingModule,
],
})
export class AccountingModule {}Why Angular 17?
The ERP was greenfield in 2023. Angular's strict TypeScript integration, strong DI, and NgRx ecosystem were a natural fit for a large enterprise app maintained by multiple teams.
Monorepo Tooling
Nx
Version: 17.x
Role: Monorepo build system for MFE-Apps/ workspace — task orchestration, computation caching, affected-change detection
Workspace root: MFE-Apps/
Key Nx features used:
| Feature | Usage |
|---|---|
affected | Only rebuild/test apps affected by a commit |
| Computation cache | Skip re-running tasks whose inputs haven't changed |
@nx/angular/module-federation | Generate and manage Module Federation webpack configs |
| Dependency graph | nx graph — visualise app/lib relationships |
| Custom executors | Deploy to Azure SWA after build |
bash
# Build only apps affected by changes since main
nx affected --target=build --base=main --head=HEAD
# Run all tests, using cache
nx run-many --target=test --all
# Visualise the dependency graph
nx graphWhy Nx?
10 Angular apps share 3 libraries. Nx eliminates full-workspace rebuilds on every commit by tracking which apps actually changed.
Micro-Frontend Architecture
Webpack Module Federation
Version: Webpack 5 (bundled with Angular CLI 17)
Role: Runtime integration of 9 remote Angular apps into the erp-home shell host
Integration: @nx/angular/module-federation wraps Webpack's ModuleFederationPlugin
Host/remote topology:
| App | Role | Port | SWA Name (pattern) |
|---|---|---|---|
erp-home | Host shell | 4401 | mic-erp-fr-{env}-home-swa |
apps-accounting | Remote | 4402 | mic-erp-fr-{env}-accounting-swa |
apps-hr | Remote | 4403 | mic-erp-fr-{env}-hr-swa |
apps-finance | Remote | 4404 | mic-erp-fr-{env}-finance-swa |
apps-sales | Remote | 4405 | mic-erp-fr-{env}-sales-swa |
apps-purchase | Remote | 4406 | mic-erp-fr-{env}-purchase-swa |
apps-inventory | Remote | 4407 | mic-erp-fr-{env}-inventory-swa |
app-distribution | Remote | 4408 | mic-erp-fr-{env}-distribution-swa |
fixed-assets | Remote | 4409 | mic-erp-fr-{env}-fixed-assets-swa |
bussiness-owners | Standalone | 4301 | mic-erp-fr-{env}-bo-swa |
Each remote exposes exactly one entry point — ./Module. The host loads remote modules lazily via Angular Router.
typescript
// erp-home routing — lazy loading a remote module
{
path: 'accounting',
loadChildren: () => loadRemoteModule({
remoteEntry: `${environment.accountingUrl}/remoteEntry.js`,
remoteName: 'apps-accounting',
exposedModule: './Module',
}).then(m => m.AppModule),
},Remote URL injection: Environment-specific URLs are stored in environment.ts files and injected at build time. No runtime URL resolution.
State Management
NgRx
Version: 17.x
Role: Centralised reactive state management — actions, reducers, effects, selectors
Used in: All 10 Angular apps
NgRx packages used:
| Package | Purpose |
|---|---|
@ngrx/store | Immutable state container |
@ngrx/effects | Side effects (HTTP calls, notifications) |
@ngrx/entity | Normalised collection management |
@ngrx/router-store | Route state in the store |
@ngrx/store-devtools | Redux DevTools integration (dev only) |
typescript
// Typical feature slice structure
// actions
export const loadInvoices = createAction('[Invoice] Load');
export const loadInvoicesSuccess = createAction(
'[Invoice] Load Success',
props<{ invoices: Invoice[] }>()
);
// reducer
export const invoiceReducer = createReducer(
initialState,
on(loadInvoicesSuccess, (state, { invoices }) =>
invoiceAdapter.setAll(invoices, { ...state, loading: false }))
);
// effect
loadInvoices$ = createEffect(() =>
this.actions$.pipe(
ofType(loadInvoices),
switchMap(() => this.invoiceService.getAll().pipe(
map(invoices => loadInvoicesSuccess({ invoices })),
catchError(err => of(loadInvoicesFailure({ error: err.message })))
))
)
);Internationalisation
@ngx-translate
Version: 15.x
Role: Runtime i18n — Arabic (RTL) and English (LTR) with hot-switching
Translation files: FrontApps/libs/shared-assets/i18n/
Supported locales:
| Locale | Direction | File |
|---|---|---|
ar | RTL | ar.json |
en | LTR | en.json |
typescript
// Translate pipe in templates
<h1>{{ 'INVOICE.TITLE' | translate }}</h1>
// Service injection in components
constructor(private translate: TranslateService) {
this.translate.use('ar'); // switch to Arabic at runtime
}RTL support: Angular CDK's Directionality service is used with a CSS class on <body> (dir="rtl") applied by the shell app on language switch. PrimeNG's appendTo="body" overlays respect the dir attribute automatically.
UI Component Library
PrimeNG
Version: 17.x
Role: Primary enterprise UI component set — data tables, forms, dialogs, charts
Theme: Custom Microtec theme extending lara-light-blue
Used in: All Angular apps
Most-used components:
| Component | Use case |
|---|---|
p-table | Data grids with lazy loading, sort, filter, pagination |
p-dialog | Modal forms and confirmations |
p-dropdown / p-multiSelect | Select controls |
p-calendar | Date and date-range pickers |
p-chart | KPI dashboards (Chart.js wrapper) |
p-toast | Notification toasts |
p-fileUpload | Document attachment upload |
p-treeTable | Hierarchical data (chart of accounts) |
typescript
// DataTable with lazy loading — standard pattern across all list pages
<p-table
[value]="invoices$ | async"
[lazy]="true"
[paginator]="true"
[rows]="20"
[totalRecords]="total$ | async"
(onLazyLoad)="store.dispatch(loadInvoices($event))">CSS Framework
Bootstrap 5
Version: 5.x
Role: Grid system, utilities, and spacing — used alongside PrimeNG components
SCSS: Customised via _variables.scss overrides (colours, fonts, breakpoints)
Used in: Layout wrappers, page scaffolding, responsive grid
PrimeNG vs Bootstrap
PrimeNG provides interactive components (tables, forms, dialogs). Bootstrap provides the page grid, spacing utilities, and non-interactive layout. Both coexist without conflict — PrimeNG components are scoped with BEM class names; Bootstrap is utility-only.
Reactive Programming
RxJS
Version: 7.x (bundled with Angular 17)
Role: Async data streams — HTTP calls, store selectors, form value changes
Operators most used: switchMap, mergeMap, combineLatest, debounceTime, distinctUntilChanged, catchError, takeUntil
typescript
// Standard component pattern — auto-unsubscribe on destroy
export class InvoiceListComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
invoices$ = this.store.select(selectAllInvoices);
loading$ = this.store.select(selectInvoicesLoading);
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}Authentication (Frontend)
Keycloak-js
Version: 22.x
Role: OIDC client library — connects Angular apps to Keycloak
Realms used:
| App type | Keycloak Realm |
|---|---|
| ERP apps (4401–4409) | microtec |
| Business Owner (4301) | businessowner |
Integration: An APP_INITIALIZER factory runs keycloak.init() before the app bootstraps. All HTTP calls inject the Bearer token via a KeycloakBearerInterceptor.
typescript
// main.ts bootstrap with Keycloak
KeycloakService.init({
url: environment.keycloakUrl,
realm: environment.keycloakRealm,
clientId: environment.keycloakClientId,
initOptions: { onLoad: 'login-required', checkLoginIframe: false },
})Testing
Jest
Version: 29.x
Role: Unit and component testing runner
Configuration: Per-app jest.config.ts files generated by Nx
Cypress
Version: 13.x
Role: End-to-end browser automation tests
Used in: Accounting and HR apps (critical user journeys)
Angular Testing Library
Version: 15.x
Role: Behaviour-driven component tests — queries by role, label, text rather than CSS selectors
Build Tooling
Angular CLI
Version: 17.x (via Nx)
Builder: @nx/angular:webpack-browser (Module Federation build)
Output: Static assets (index.html, chunked JS, CSS, assets)
Environment files:
| File | Environment |
|---|---|
environment.ts | Development (local) |
environment.stage.ts | Stage |
environment.cloud.ts | Cloud (preprod/uat) |
environment.prod.ts | Production |
Shared Libraries
| Library | Path | Purpose |
|---|---|---|
shared-lib | FrontApps/libs/shared-lib | Common components, services, pipes, directives |
shared-assets | FrontApps/libs/shared-assets | i18n JSON files, images, icons |
microtec-auth-lib | FrontApps/libs/microtec-auth-lib | Keycloak wrapper, token management |
apps-shared-lib | FrontApps/libs/apps-shared-lib | ERP-domain shared models and helpers |
Not in Module Federation shared scope
These libraries are bundled inside each remote app rather than being declared in the Module Federation shared config. This avoids version-mismatch runtime errors at the cost of duplicated bytes. Each remote is independently deployable.