Appearance
Frontend Developer Onboarding
A practical guide to becoming productive on the Microtec ERP Angular micro-frontend.
Prerequisites: Complete Day 1 Checklist first
Stack: Angular 17, Nx workspace, NgRx, PrimeNG, Bootstrap 5, NgModule architecture
Workspace Overview
The frontend lives in the MFE-Apps/ submodule (Nx workspace) and FrontApps/ (legacy workspace):
MFE-Apps/ ← Primary: Nx-managed micro-frontend workspace
├── projects/
│ ├── apps-accounting/ # Accounting module (port 4402)
│ ├── apps-hr/ # HR module (port 4403)
│ ├── apps-finance/ # Finance module (port 4404)
│ ├── apps-sales/ # Sales module (port 4405)
│ ├── apps-purchase/ # Purchase module (port 4406)
│ ├── apps-inventory/ # Inventory module (port 4407)
│ ├── app-distribution/ # Distribution module (port 4408)
│ ├── fixed-assets/ # Fixed assets module (port 4409)
│ ├── erp-home/ # ERP portal shell (port 4401)
│ └── bussiness-owners/ # Business owner portal (port 4301)
└── libs/
├── shared-lib/ # Shared components, services, auth logic
└── shared-assets/ # i18n translations, images, fonts[INFO]
FrontApps/contains an older version of the same workspace. For new features, work inMFE-Apps/. Check with your team lead which one is current for your sprint.
Getting Started
Start a single app (recommended for daily development)
bash
cd ~/Projects/microtec/Erp/MFE-Apps
# Install dependencies
npm i --f
# Start the Accounting app
npx nx serve accounting
# App opens at http://localhost:4402Start all apps simultaneously
bash
# [WARNING] Requires ~32 GB RAM — not recommended on 16 GB machines
npm run start:allRecommended: Start only what you need
bash
# Start ERP home shell + the module you're working on
npx nx serve erp-home &
npx nx serve apps-accountingApp Architecture
Each app follows Module Federation — independently deployable Angular apps that load into the ERP shell at runtime.
erp-home (shell)
├── Lazy loads → apps-accounting (remote)
├── Lazy loads → apps-hr (remote)
├── Lazy loads → apps-inventory (remote)
└── ... (other remotes)Key architectural rules
- NgModule-based architecture only —
standalone: falseon all components. Do NOT create standalone components. - No direct cross-app imports — use the shell's routing or shared-lib instead.
- All shared UI components go in
libs/shared-lib/src/lib/components/ - All auth logic uses
microtec-auth-libfrom shared-lib — never implement auth directly in an app.
Module Structure
Inside each app (apps-accounting as example):
apps-accounting/
├── src/
│ ├── app/
│ │ ├── app.module.ts # Root NgModule
│ │ ├── app-routing.module.ts # Feature routes
│ │ └── features/
│ │ ├── invoices/
│ │ │ ├── invoices.module.ts
│ │ │ ├── invoices-routing.module.ts
│ │ │ ├── components/
│ │ │ │ ├── invoice-list/
│ │ │ │ └── invoice-form/
│ │ │ ├── services/
│ │ │ │ └── invoice.service.ts
│ │ │ └── store/ # NgRx
│ │ │ ├── invoice.actions.ts
│ │ │ ├── invoice.reducer.ts
│ │ │ ├── invoice.effects.ts
│ │ │ └── invoice.selectors.ts
│ │ └── ...
│ └── environments/
│ ├── environment.ts # development
│ ├── environment.stage.ts # stage
│ ├── environment.cloud.ts # cloud/preprod
│ └── environment.prod.ts # productionShared Libraries
shared-lib — common UI components
typescript
// Import shared components via the library
import { MicrotecTableModule } from '@microtec/shared-lib';
import { MicrotecFormModule } from '@microtec/shared-lib';
import { MicrotecDialogModule } from '@microtec/shared-lib';typescript
// Auth service — always use this, never roll your own
import { AuthService } from '@microtec/shared-lib/auth';
@Component({...})
export class MyComponent {
constructor(private auth: AuthService) {}
getCurrentUser() {
return this.auth.currentUser$; // Observable<User>
}
}shared-assets — i18n translations
Add translation keys to both language files:
json
// shared-assets/i18n/en.json
{
"invoice": {
"title": "Invoices",
"form": {
"number": "Invoice Number",
"total": "Total Amount"
}
}
}json
// shared-assets/i18n/ar.json
{
"invoice": {
"title": "الفواتير",
"form": {
"number": "رقم الفاتورة",
"total": "المبلغ الإجمالي"
}
}
}Usage in templates:
html
<h1>{{ 'invoice.title' | translate }}</h1>
<label>{{ 'invoice.form.number' | translate }}</label>State Management with NgRx
Every feature that communicates with the API uses NgRx:
typescript
// actions
export const loadInvoices = createAction('[Invoice] Load Invoices');
export const loadInvoicesSuccess = createAction('[Invoice] Load Invoices Success',
props<{ invoices: Invoice[] }>());
export const loadInvoicesFailure = createAction('[Invoice] Load Invoices Failure',
props<{ error: string }>());
// effects
@Injectable()
export class InvoiceEffects {
loadInvoices$ = createEffect(() =>
this.actions$.pipe(
ofType(loadInvoices),
switchMap(() => this.invoiceService.getAll().pipe(
map(invoices => loadInvoicesSuccess({ invoices })),
catchError(err => of(loadInvoicesFailure({ error: err.message })))
))
)
);
constructor(private actions$: Actions, private invoiceService: InvoiceService) {}
}UI Components
The platform uses PrimeNG for complex UI components and Bootstrap 5 for layout:
html
<!-- PrimeNG DataTable with pagination -->
<p-table [value]="invoices$ | async"
[paginator]="true"
[rows]="10"
[loading]="loading$ | async">
<ng-template pTemplate="header">
<tr>
<th>{{ 'invoice.form.number' | translate }}</th>
<th>{{ 'invoice.form.total' | translate }}</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-invoice>
<tr>
<td>{{ invoice.number }}</td>
<td>{{ invoice.total | currency:'SAR' }}</td>
</tr>
</ng-template>
</p-table>
<!-- Bootstrap layout -->
<div class="container-fluid">
<div class="row">
<div class="col-md-6">...</div>
<div class="col-md-6">...</div>
</div>
</div>Styling Conventions
- Use SCSS for all component styles
- BEM naming convention for custom CSS classes:
.invoice-form__field--error - Use CSS variables from the Microtec design tokens:scss
.my-component { color: var(--primary-color); // Microtec brand primary background: var(--surface-ground); // PrimeNG surface token border-radius: var(--border-radius); } - RTL support is built in via Angular CDK — always test in both
dir="ltr"anddir="rtl"
Running Tests
bash
cd MFE-Apps
# Run tests for a specific project
npx nx test apps-accounting
# Run with coverage
npx nx test apps-accounting --coverage
# Run all tests
npx nx run-many --target=test --all
# Watch mode (recommended during TDD)
npx nx test apps-accounting --watchBuilding for Production
bash
# Build a single app
npx nx build apps-accounting --configuration=production
# Build all apps
npm run build:all:prod
# Output goes to: dist/apps/apps-accounting/Environment Configuration
Each app has 4 environment files. The active one is selected at build time:
| File | ng build configuration | Deployed to |
|---|---|---|
environment.ts | development (default) | Local dev |
environment.stage.ts | stage | Stage |
environment.cloud.ts | cloud | Preprod / UAT |
environment.prod.ts | production | Production |
typescript
// environment.ts example
export const environment = {
production: false,
apiBaseUrl: 'http://localhost:5000',
keycloakUrl: 'http://localhost:8080/auth',
realm: 'microtec',
clientId: 'apps-accounting'
};Nx Useful Commands
bash
# Show dependency graph
npx nx graph
# Lint a project
npx nx lint apps-accounting
# Generate a new component
npx nx g @schematics/angular:component features/invoices/components/invoice-form \
--project=apps-accounting \
--module=invoices
# Generate a new service
npx nx g @schematics/angular:service features/invoices/services/invoice \
--project=apps-accounting
# Show affected projects (useful before running tests)
npx nx affected:graphCommon Issues
| Problem | Solution |
|---|---|
npm i fails | Use npm i --f (force) |
| Module federation error on startup | Start the shell app (erp-home) before remote apps |
| i18n key not translating | Check both ar.json and en.json have the key |
standalone: true component breaks | Use standalone: false — project standard |
| CORS error calling API | Use the API proxy config in proxy.conf.json |
NgRx action not dispatched | Check that the module imports StoreModule.forFeature(...) |