Focaloid Engineering Case Study

Asset Integrity Frontend

One interface where engineers enter and review industrial asset integrity data, manually recorded and automatically streamed alike, define their own calculated metrics, watch live sensor trends, and act on alerts, without waiting on a backend release for every change.

Angular 17 NgRx TypeScript Marine classification

Drawn from the application's architectural knowledge-transfer documentation. It covers the front-end application layer; the client's name, product name and internal identifiers have been redacted or generalized.

ScopeFront end
8
feature modules behind one shell
6
route-scoped NgRx feature stores
5
chained HTTP interceptors
v17
Angular, standalone-first
One application

Eight feature modules behind a single /home shell, sharing one auth, state and HTTP convention.

State that ends with the route

Six NgRx feature stores provided on the route definition, created fresh on every visit.

Python in the browser

Engineers author, test and deploy their own calculated tags without a platform release.

Permission-driven UI

Unauthorized elements are removed from the DOM, not merely disabled.

The client

The surface engineers actually use.

The client operates across marine classification, certification and engineering assurance for industrial assets. Its platform gives engineering and operations teams a way to monitor the integrity of physical assets over time.

This application is the single-page frontend that sits at the front of that platform: where engineers record measurements, watch tag data, define their own metrics and respond to alerts.

What it monitors
The assets

Pipe sections, vessels and similar plant

Physical industrial assets whose integrity is tracked over time.

The users

Engineering and operations teams

Recording measurements, watching tag data, defining metrics and responding to alerts.

The challenge

Four demands that a conventional dashboard could not meet.

Challenge 01

Manual and automated data on equal footing

Some measurements come in as live tag streams; others are recorded by hand or uploaded in bulk from a CSV. Both had to feed the same Item of Work records and the same historical view, through one consistent entry point.

Challenge 02

Engineer-defined metrics, without a backend release cycle

Standard tags do not cover every asset's needs. Engineers needed to define their own calculated tags, as real logic expressed in Python, test them, and put them live without waiting on a platform deployment.

Challenge 03

Live monitoring alongside historical trends, in the same screen

Real-time tag values and paginated historical data had to coexist without either one blocking or overwhelming the interface.

Challenge 04

Precise, role-driven access across many features

With eight feature areas and multiple tenants, a user's access needed to be enforced by role and feature permission, not by which links happened to be visible.

The mandate

Four questions, fixed before the build started.

Question 01

Data entry

How do engineers capture manual and automated measurement data through one consistent interface, single record or bulk upload alike?

Question 02

Custom metrics

How do engineers define and safely deploy their own calculated tags, as real Python logic, without a full platform release?

Question 03

Live and historical

How is real-time tag monitoring delivered alongside historical trend analysis, in the same workspace, without either one degrading the other?

Question 04

Access control

How is access controlled precisely enough that a user only ever sees the assets and features their role actually permits?

The solution

One Angular 17 application, built feature by feature.

Standalone, lazy-loaded modules behind a shared shell, each answering one of the four questions.

Q1 · Data entry
Manual Inputs moduleGuided single and bulk forms for IOWs, measurements and tags, sharing one drag-and-drop upload component with validation and progress feedback.
Q2 · Custom metrics
Calculated Tags moduleAn in-browser Python console, a test, deploy and undeploy workflow, and a navigation guard that protects unsaved scripts.
Q3 · Live and historical
Tag List moduleLive values streamed over Server-Sent Events, alongside a configurable, paginated historical view with Excel export.
Q4 · Access control
Azure AD B2C, plus a permissions directiveRole and feature permissions fetched from an internal admin API, enforced by a directive that removes, not just disables, unauthorized UI.

Everything else, from KPI dashboards and alert management to embedded Power BI reporting, sits alongside these four as peer feature modules behind the same shell, sharing the same auth, state and HTTP conventions.

Architecture

Every request begins the same way.

Sign-in dispatches through a custom authentication library wrapping Azure AD B2C, and the resulting bearer token is attached to every outbound call by a dedicated interceptor.

The shell and its feature modules

Feature modules are lazy-loaded under a single /home shell that hosts the toolbar, side navigation and router outlet. It is the one part of the application still built as a traditional NgModule, because it is the shell every other route mounts into.

Switching the active asset in the toolbar resets feature state application-wide, so no screen can show stale data for the wrong asset.

Store slices
Route-scoped
iowListFeature manualInputsFeature calculatedTagsFeature tagListFeature dataExportFeature filterFeature

Provided only while the feature's route is active.

Root
auth system

Persist across the whole session.

The path a request takes
IdentityAzure AD B2CSign-in through a custom library, with a Keycloak adapter retained as a fallback in some environments
TransportTyped HTTP service layerBearer token attached by the auth interceptor; the layer also calls out to the client platform
StateNgRx Effects to feature storeScoped to the active route, then rendered by smart and dumb components
Inside the build

Five decisions that shaped the application.

Change A

Route-scoped state instead of one global store

Problem
Six feature areas each carry meaningfully different state shapes; a single global store would either bloat or leak memory between navigations.
Approach
Provide each feature's NgRx store directly on its route definition.
Technology
NgRx 17 createFeature / createReducer / createEffect, wired in via provideState()
Benefit
A feature's state exists only while its route is active, and is created fresh on every visit.
Change B

In-browser Python authoring for calculated tags

Problem
Engineers need custom metrics the standard tag set does not cover, without waiting on a backend deployment.
Approach
An in-browser code editor for writing the tag's Python logic, with a server-side dry-run before anything goes live.
Technology
Ace Editor configured for Python, a test / deploy / undeploy workflow, and a route guard
Benefit
A tag can be authored, tested and deployed entirely from the browser, and an unsaved script cannot be lost to an accidental navigation.
Change C

Real-time tag streaming next to historical trends

Problem
Live sensor values and paginated historical data need to sit in the same view without either blocking the other.
Approach
A dedicated real-time component subscribes to a push stream independently of the historical table's request and response cycle.
Technology
Server-Sent Events via an EventSource polyfill, wrapped as an RxJS observable
Benefit
Live and historical data update on their own schedules, with no polling loop for the live side.
Change D

Permission-driven UI via a structural directive

Problem
A disabled control still reveals that a feature exists to a user who is not permitted to use it.
Approach
Fetch role and feature permissions from an internal admin API at login, and remove unauthorized elements from the DOM rather than just disabling them.
Technology
A shared AccessPermissionsDirective reading a typed permissions enum
Benefit
Access is enforced by what the server says a user can do, not by what the client chooses to show.
Change E

A layered HTTP interceptor chain

Problem
Auth, loading state and success or error feedback all cut across every feature, and repeating that logic per call invites drift.
Approach
Compose five interceptors in a fixed order: auth token injection, request metadata, a separate Power BI token flow, a loading-state counter with URL exclusions, and a response-driven notification toast.
Technology
Angular's HTTP interceptor chain; token caching via RxJS shareReplay
Benefit
Every feature gets consistent auth, loading indicators and feedback for free, without repeating the logic per call.
Key technical decisions

Five decisions, each with the reason attached.

Because six feature areas needed independent, disposable state Each is provided as its own NgRx feature store directly on the route definition, rather than added to one growing root store.
Because the codebase was migrating gradually off NgModules Every feature was rebuilt as standalone components loaded via loadComponent(); only the Home shell remains a traditional NgModule, since it is the one piece every route mounts into.
Because the application is a public client with no secure place to store a secret The OAuth2 authorization code exchange uses PKCE, deliberately avoiding an eval-based token-exchange implementation.
Because a calculated tag script represents real engineering work that is easy to lose to an accidental click A dedicated route guard blocks navigation away from the Calculated Tags screen until an unsaved script is explicitly saved or discarded.
Because switching the monitored asset should never leave stale data behind The global asset picker resets feature state across the whole application on every switch, rather than leaving each feature to manage that itself.
Security and reliability

Enforced server-side, and removed from the DOM.

As documented in the application's own architectural reference.

  • Every API request carries an Azure AD B2C bearer token, attached by a dedicated interceptor.
  • Roles and feature permissions are fetched server-side and enforced by hiding UI elements outright, not merely disabling them.
  • The OAuth2 authorization code exchange uses PKCE, with an explicit note in the reference documentation that the implementation avoids eval.
  • File storage access uses time- and scope-limited SAS tokens rather than open blob access.
  • A dedicated pipe for rendering trusted HTML is used sparingly, and only for server-sourced content.
  • Pre-commit hooks enforce linting before code is committed.
Scope of this claim

These practices are as described in the project's architectural knowledge-transfer documentation. This case study is based on that reference rather than an independent code-level security review.

Testing and validation

Unit tests and static analysis, enforced at commit.

The documented verification workflow covers unit testing and static analysis, and this section says plainly where that documentation stops.

Documented

Karma, ESLint, Husky

Karma-based unit tests via ng test, and ESLint checks via ng lint, with Husky enforcing lint checks at pre-commit time.

Not documented

Coverage, integration, end to end

Coverage thresholds, integration testing and end-to-end testing strategy are not described in the reviewed documentation.

Deployment and environments

Three configurations, one file-replacement mechanism.

Three environment configurations are defined, each with its own configuration file and gateway target, built through Angular's standard environment file-replacement mechanism.

Environments
development test production
Build commands
ng build ng build --configuration=test
Not covered

CI/CD pipeline details, hosting infrastructure and release process sit outside this application-layer documentation.

Outcomes

What the application can do.

Read this first

Outcomes here are architectural and capability-based. No adoption, performance or business-impact figures are available in the source documentation.

  • CapabilityManual entry, bulk upload, automated tag ingestion, live monitoring, KPI reporting, alerting and embedded BI reporting are unified in one application, behind one authentication and permission model.
  • CapabilityEngineers can author, test and deploy their own calculated metrics directly in the browser, without a platform release.
  • CapabilityFeature-level state is created and destroyed with the route it belongs to, keeping memory use tied to what is actually on screen.
  • CapabilityAccess to every feature and every asset is enforced by role and permission, evaluated server-side.
Technology stack

Ten layers, one application.

Core framework
Angular 17.1.0, standalone-firstSingle-page application shell and routing.
Language
TypeScript 5.2.2Type-safe application code.
State management
NgRx 17.1.0Root and route-scoped feature state, effects for all HTTP side effects.
UI components
Angular Material 16.2.13, Bootstrap 5.3.2Component library and responsive layout.
Data visualization
Apache ECharts 5.5.0, AG Grid 31.3.2, Leaflet 1.9.3Charts, enterprise data tables and geospatial maps.
Authentication
Azure AD B2C (OIDC), Keycloak fallbackSign-in, token issuance, and role and feature permission lookup.
Embedded reporting
Power BI Client 2.22.3In-application BI report embedding.
Storage
Azure Blob StorageDocument and file storage, accessed via scoped SAS tokens.
Internationalization
ngx-translate 15.0.0Multi-language labels and messages.
Editor and export
Ace Editor, XLSX, jsPDF, html2canvasIn-browser Python authoring for calculated tags, and Excel, PDF and screenshot exports.
Let us build

Are your engineers waiting on a release to define a metric?

One interface for asset integrity data that arrives in every form, hand-entered, bulk-uploaded or streamed live, with the controls to let engineers define their own metrics safely.