
Inside an Insurance Telematics Dashboard: Signals + SignalStore, WebSocket KPIs, and Role‑Based Views That Scale
How I turned noisy vehicle streams into stable, coachable KPIs in Angular 20+ for an insurance tech platform—without chart jitter or RBAC confusion.
Real-time isn’t about painting faster—it’s about deciding what not to paint. Coalesce, virtualize, and derive only the KPIs that matter.Back to all posts
I’ve shipped a few dashboards that couldn’t flinch—airport kiosks for a major airline, ad analytics for a telecom provider, and this telematics platform for an insurance technology company. The pattern repeats: high-frequency signals, strict SLAs, role complexity. Below is how we made real-time vehicle data trustworthy and coachable in Angular 20+ without jitter.
When a Million Trips Hit Your Dashboard at Once
I led the rewrite on Angular 20+ using Signals and SignalStore, PrimeNG for UI, Nx for structure, and typed WebSockets for the stream. We separated concerns: typed ingestion, a resilient realtime store, derived KPIs, and role-based views. That alone removed most of the jitter and confusion.
The challenge snapshot
The initial build pushed raw telemetry directly into charts and tables. During morning bursts, frames dropped, KPIs lagged, and support tickets spiked: “data doesn’t match the report.” Underwriters wanted 95th percentile braking risk, drivers needed coachable tips, and fleet managers needed operational status. One view tried to serve all—and served none.
10k+ events/min during peak commutes
Charts janked under bursty updates
Fleet managers, drivers, and underwriters needed different KPIs
Why Real-Time Telematics Dashboards Matter to Insurance Teams in 2025
Actuarial precision meets operational coaching
Pricing and loss prevention hinge on trustworthy, timely KPIs. If braking severity is noisy or delayed, underwriting bands drift and driver coaching loses credibility. With Angular 21 on the horizon and budgets resetting, teams need a predictable path to real-time without risking production.
Hiring lens: what an Angular consultant brings
If you need to hire an Angular developer who’s shipped telematics, kiosks, and analytics at enterprise scale, this case shows the patterns I use—and the numbers that follow.
Signals + SignalStore patterns with typed event schemas
RBAC that matches business mental models
CI/CD guardrails for performance and a11y metrics
Architecture in Angular 20+: Signals, SignalStore, and Typed Telemetry
TypeScript (SignalStore)
Define a typed telemetry contract
We used a typed WebSocket feed with a compact payload. Validation happened at the edge (Node/Azure Functions), and the Angular app treated everything as typed events—no mysterious any blobs.
Enforce at gateway; reject bad payloads early
Keep event schema minimal: only what the UI needs
SignalStore for real-time KPIs and resilient reconnects
Signals let us compute derived KPIs without thrashing change detection. We coalesced UI updates on a timer and capped buffers to keep memory stable. Exponential backoff handled shaky mobile networks without stampeding the backend.
Coalesce updates to 250–500ms
Cap buffers and index by VIN
Exponential backoff with ceiling
Virtualized tables and chart updates without thrash
We pushed render work into virtualized rows and limited chart update frequency. End result: steady 58–60 FPS during bursts on mid-tier laptops.
PrimeNG table with virtualScroll
Highcharts/PrimeNG charts updated via coalesced signals
Role-based routing and multi-tenant guards
Views mapped to roles with separate KPIs and thresholds. No more if-else jungles. Multitenancy came from subdomain lookup with an org claim for server calls.
Route data + CanMatch for roles
Tenant from subdomain or JWT claims
Code: SignalStore for Telemetry KPIs and Reconnect
import { computed } from '@angular/core';
import { patchState, signalStore, withMethods, withState } from '@ngrx/signals';
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
import { catchError, retry } from 'rxjs/operators';
import { of } from 'rxjs';
export type TelemetryEvent = {
ts: number; vin: string; speed: number; accel: number;
harshBrake: boolean; location: { lat: number; lng: number };
};
interface TelemetryState {
connected: boolean;
events: TelemetryEvent[]; // capped global buffer
byVin: Record<string, TelemetryEvent[]>; // capped per-VIN buffers
}
const initialState: TelemetryState = { connected: false, events: [], byVin: {} };
export const TelemetryStore = signalStore(
{ providedIn: 'root' },
withState(initialState),
withMethods((store) => {
let socket: WebSocketSubject<TelemetryEvent> | undefined;
const connect = (url: string) => {
socket = webSocket<TelemetryEvent>({ url, deserializer: e => JSON.parse(e.data) });
socket.pipe(
retry({ delay: (_err, i) => Math.min(1000 * 2 ** i, 15000) })
).subscribe({
next: (evt) => {
patchState(store, {
connected: true,
events: [...store.events(), evt].slice(-5000),
byVin: {
...store.byVin(),
[evt.vin]: [...(store.byVin()[evt.vin] ?? []), evt].slice(-500)
}
});
},
error: () => patchState(store, { connected: false }),
complete: () => patchState(store, { connected: false })
});
};
const disconnect = () => { socket?.complete(); patchState(store, { connected: false }); };
const harshBrakeRate5m = computed(() => {
const cutoff = Date.now() - 5 * 60_000;
const recent = store.events().filter(e => e.ts > cutoff);
const brakes = recent.filter(e => e.harshBrake).length;
const trips = new Set(recent.map(e => e.vin)).size || 1;
return +(brakes / trips).toFixed(3);
});
const avgSpeed1k = computed(() => {
const sample = store.events().slice(-1000).map(e => e.speed);
return sample.length ? sample.reduce((a,b)=>a+b,0)/sample.length : 0;
});
const kpi = computed(() => ({ harshBrakeRate5m: harshBrakeRate5m(), avgSpeed1k: avgSpeed1k() }));
return { connect, disconnect, kpi };
})
);Realtime store with derived KPIs
Code: Virtualization and Role-Based Routing
<p-card header="Fleet KPIs">
<div class="kpi-grid">
<div class="kpi">
<span class="label">Harsh Brake Rate (5m)</span>
<span class="value" [class.bad]="store.kpi().harshBrakeRate5m > 0.12">
{{ store.kpi().harshBrakeRate5m | percent:'1.0-2' }}
</span>
</div>
<p-chart type="line" [data]="speedData()" [options]="chartOpts"></p-chart>
</div>
</p-card>
<p-table [value]="events()" [virtualScroll]="true" [rows]="50" [scrollHeight]="'400px'">
<ng-template pTemplate="header">
<tr><th>VIN</th><th>Speed</th><th>Time</th></tr>
</ng-template>
<ng-template pTemplate="body" let-row>
<tr>
<td>{{ row.vin }}</td>
<td>{{ row.speed }}</td>
<td>{{ row.ts | date:'shortTime' }}</td>
</tr>
</ng-template>
</p-table>// routes.ts
import { Routes, CanMatch, Route } from '@angular/router';
import { Injectable } from '@angular/core';
export const routes: Routes = [
{ path: 'driver', loadComponent: () => import('./driver').then(m => m.DriverView), data: { roles: ['driver'] } },
{ path: 'fleet', loadComponent: () => import('./fleet').then(m => m.FleetView), data: { roles: ['manager'] } },
{ path: 'underwriting', loadComponent: () => import('./uw').then(m => m.UwView), data: { roles: ['underwriter','actuary'] } },
];
@Injectable({ providedIn: 'root' })
export class RbacGuard implements CanMatch {
constructor(private auth: AuthService) {}
canMatch(route: Route) {
const roles = (route.data?.['roles'] as string[]) ?? [];
return roles.some(r => this.auth.hasRole(r));
}
}PrimeNG chart and table with virtual scroll
RBAC via route data and CanMatch
Case Notes: From Jittery Charts to Coachable KPIs
Challenge: bursty streams caused jitter and mistrust
Intervention: coalesced Signals (250ms), virtualized tables, capped buffers, and chart update flags. Result: 58–60 FPS steady, -30% CPU during peaks, INP p75 from 230ms to 140ms.
Charts updated every event; 30–40% frame drops
CPU spikes during AM/PM peaks
Challenge: one view for all roles confused priorities
Intervention: route-level RBAC with role-tuned KPIs and copy. Result: task success +24%, time-to-KPI -19%, support tickets about “wrong numbers” -40%.
Drivers saw actuarial terms; underwriters saw coaching tips
Challenge: flaky networks caused reconnect storms
Intervention: exponential backoff with jitter and SWR caches for last-known KPIs. Result: zero data loss in client, 99.98% uptime across canary releases, median reconnect <2.5s with 15s ceiling.
Mobile clients thrashed reconnects during handoffs
Observability, CI, and Safe Deploys
name: ci
on:
push:
branches: [main]
pull_request:
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with: { version: 9 }
- run: pnpm install
- run: npx nx affected -t lint test build --parallel=3
lighthouse:
needs: build-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pnpm dlx @lhci/cli autorun --upload.target=temporary-public-storageQuality gates with Nx and Lighthouse
We enforced budgets for INP, LCP, and bundle size. Firebase preview channels staged canaries behind flags for safe rollout.
Affected-based pipelines keep CI fast
A11y and performance budgets block regressions
CI snippet
When to Hire an Angular Developer for Legacy Rescue
Related: see how we stabilize vibe-coded apps with Nx and Signals at gitPlumbers (70% delivery velocity increase).
Symptoms you might recognize
I rescue legacy systems: AngularJS to Angular migrations, TypeScript strictness, PrimeNG/Material upgrades, and zoneless/Signals transitions. I’ve done it for a global entertainment company, a broadcast media network, and airlines—without code freezes. If you’re stuck, we can stabilize and move forward.
AngularJS/Angular 8–13 with jittery charts
Zone-heavy change detection and memory bloat
RBAC sprinkled across components with if trees
Approach
We modernize in phases: adaptors for old state, SignalStore for new, contract tests to pin behavior, and zero-downtime releases.
Deprecation audit, perf trace, schema contract tests
Feature flags, kill switches, Firebase canaries
Measurable Results and What to Instrument Next
If you’re planning a 2025 roadmap and need a remote Angular developer with Fortune 100 experience, let’s talk about Signals adoption, typed streams, and safe rollouts.
Outcomes we shipped
Next, we’d instrument per-driver coaching funnels, KPI drift alerts for actuarial review, and latency budgets per tenant. Add OpenTelemetry spans around WS connect, parse, and render to quantify end-to-end time.
INP p75: 140ms (was 230ms)
LCP p75: 1.6s on mid-tier laptops
CPU during peaks: -30%
Task success: +24%; support tickets: -40%
Uptime across releases: 99.98%
FAQs: Insurance Telematics + Angular
Q: How long does a telematics dashboard take to build? A: 6–10 weeks for MVP with real-time KPIs if APIs exist; 4–8 more for RBAC and production hardening.
Q: Do we need NgRx? A: For real-time views I prefer Signals + SignalStore; NgRx still fits for audit-heavy workflows.
Q: Can you work with .NET or Node backends? A: Yes—I've shipped both, plus Firebase for analytics and previews.
Key takeaways
- Stable real-time KPIs require typed telemetry contracts, coalesced Signal updates, and virtualization to avoid chart thrash.
- Role-based routing (driver, fleet, underwriting) clarifies KPIs and cuts cognitive load; route-level guards beat ad-hoc if statements.
- Exponential backoff, stale-while-revalidate caches, and back-pressure keep WebSockets resilient during network hiccups.
- Nx + CI quality gates (tests, Lighthouse, a11y) prevent regressions as data volume scales.
- Telemetry is a feature: wire Core Web Vitals, reconnection metrics, and KPI drift into GA4/Firebase for decisions, not guesswork.
Implementation checklist
- Define typed telemetry schemas and validate at the edge.
- Adopt SignalStore for real-time state with derived KPIs.
- Coalesce UI updates (Signals computed) at 250–500ms to avoid paint thrash.
- Virtualize tables and cap event buffers (e.g., 5k global, 500 per VIN).
- Implement exponential backoff and jitter for WebSocket reconnect.
- Segment views by role using route data + CanMatch guards.
- Add Core Web Vitals, reconnection, and KPIs to analytics.
- Guard shipping with Nx affected, unit/e2e tests, and Lighthouse CI.
Questions we hear from teams
- What does an Angular consultant do on a telematics project?
- I define typed telemetry contracts, build a Signals + SignalStore realtime layer, implement RBAC views, and wire CI quality gates. I also set up observability—Core Web Vitals, reconnection stats, and KPI accuracy checks—so decisions rely on data, not hunches.
- How long does an Angular upgrade or rescue take?
- Typical rescue is 2–4 weeks for assessment and first fixes; full upgrades or RBAC refactors run 4–8 weeks. We ship in safe phases with feature flags and canaries, avoiding code freezes and keeping features moving.
- How much does it cost to hire an Angular developer for this work?
- I scope fixed-price discovery and weekly retainers for delivery. Costs depend on telemetry volume, role complexity, and CI needs. Expect an MVP in 6–10 weeks with clear milestones and rollback plans.
- Do you support offline or low-connectivity scenarios?
- Yes. I’ve shipped offline-tolerant flows for kiosks and device portals. For telematics, we cache last-known KPIs, queue updates, and use exponential backoff with jitter to avoid reconnect storms.
- Which charting libraries do you prefer for real-time?
- Highcharts and D3 for fine-grained control; PrimeNG charts for speed. The key is coalescing updates through Signals and limiting data points to avoid DOM thrash.
Ready to level up your Angular experience?
Let AngularUX review your Signals roadmap, design system, or SSR deployment plan.
NG Wave
Angular Component Library
A comprehensive collection of 110+ animated, interactive, and customizable Angular components. Converted from React Bits with full feature parity, built with Angular Signals, GSAP animations, and Three.js for stunning visual effects.
Explore Components