Why Angular for enterprise platforms
Since Angular 9 on Xtend Sales, the choice has repaid itself in structure. Angular’s opinionation — modules, dependency injection, TypeScript-first, one way to do forms — is a cost on a landing page and an asset on a 200-screen ERP frontend maintained by rotating teams for years. The framework enforces the consistency that team discipline alone never sustains.
Patterns for operations UIs
- Feature modules mirroring platform domains: chargers, sessions, tariffs, partners — the frontend’s structure teaches the platform’s structure.
- Reactive forms for everything non-trivial: tariff editors and partner onboarding wizards carry validation rules that belong in code, not templates.
- RxJS for live data, used with restraint: charger status streams and session monitors are natural observables; simple CRUD stays simple promises.
- Smart/dumb component split: containers talk to services, presentational components take inputs — the pattern that keeps 200 screens testable.
The unglamorous essentials
- Tables are the product: server-side pagination, column state persistence, and export — operators live in grids eight hours a day, respect that.
- Role-based UI trimmed from the same claims the API enforces — hiding a button is UX, the API check is security; you need both.
- Loading and error states designed, not defaulted — an operator must always know whether the fleet is fine or the dashboard is stale.
- Bundle discipline with lazy-loaded routes; the sessions module should not pay for the reporting module’s chart library.
Worked example: live charger status, done calmly
RxJS where it earns its place — a fleet status stream with reconnect, share, and OnPush-friendly consumption:
@Injectable({ providedIn: 'root' })
export class ChargerStatusService {
readonly status$ = defer(() =>
webSocket<ChargerStatus>(environment.statusWsUrl)
).pipe(
retry({ delay: () => timer(3000) }), // reconnect, forever, politely
scan((fleet, update) => ({ ...fleet, [update.chargerId]: update }),
{} as Record<string, ChargerStatus>),
shareReplay({ bufferSize: 1, refCount: true })
);
}
@Component({
selector: 'fleet-board',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<charger-tile *ngFor="let c of fleet$ | async | keyvalue; trackBy: byId"
[status]="c.value"></charger-tile>'
})
export class FleetBoardComponent {
fleet$ = inject(ChargerStatusService).status$;
byId = (_: number, c: { key: string }) => c.key;
}
One shared socket for every subscriber, last state replayed to late joiners, and trackBy keeping 500 tiles from re-rendering on every heartbeat.
FAQ
React offers more flexibility; Angular offers more built-in consistency. For long-lived operations UIs with rotating enterprise teams, I keep choosing Angular — the framework carries standards the team would otherwise have to enforce manually.
OnPush change detection, trackBy on every list, lazy routes, and virtual scrolling on big grids. Most “Angular is slow” complaints are default change detection churning on 5,000-row tables.
Standalone for everything new — less ceremony, clearer dependencies. Existing module-based apps migrate incrementally, route by route, no big-bang rewrites.