
Before/After: Rescuing a Chaotic Angular Analytics App with Signals, SignalStore, Nx, and Real Metrics
A jittery media analytics dashboard became a maintainable, 60fps system—without a rewrite—by adding Signals + SignalStore, Nx boundaries, typed events, and CI guardrails.
We didn’t rewrite. We added guardrails, replaced Subject soup with SignalStore, and let real metrics steer every step.Back to all posts
I’ve been brought in to calm down more than a few jittery dashboards. The most recent: a media analytics Angular app that pegged CPU, dropped frames during WebSocket bursts, and shipped hotfixes at 10 p.m. every Friday. Leadership didn’t want a rewrite—they wanted reliability, speed, and developer sanity.
What follows is the before/after story and the exact steps I used—Signals + SignalStore, Nx, typed event schemas, PrimeNG virtualization, and CI guardrails. If you’re evaluating whether to hire an Angular developer or consultant for a legacy rescue, this is what you can expect.
A Dashboard That Jitters to 60fps: What Changed
The scene: before I touched the code
The app rendered real-time ad delivery metrics for hundreds of channels. During spikes, charts jittered, tables re-sorted mid-scroll, and the browser gasped. Engineering morale matched the flame charts. I’ve seen similar patterns at a leading telecom provider (ads analytics) and a broadcast media network (VPS scheduling). Same root cause: vibe-coded state + no boundaries.
CPU pinned during 5-minute ad breaks
Memory climbed 200–300 MB per session
INP hovered ~380 ms on table interactions
The constraints
As companies plan 2025 Angular roadmaps, budget for rewrites is rare. We needed a surgical rescue that fit inside ongoing delivery. That’s where Signals, SignalStore, and Nx shine.
No rewrite
Keep shipping features weekly
Remote-only collaboration across time zones
Why Chaotic Angular Codebases Spiral—and How to Stop Them
Common failure modes I found
The code mixed ad-hoc services emitting Subjects with components subscribing directly. No typed event contracts. PrimeNG datatable virtualization was disabled to work around a race, compounding the problem. Accessibility was a casualty of speed.
Global mutable singletons shared across tabs
Competing state models: BehaviorSubject soup + partial NgRx
Change detection thrash: no trackBy, expensive getters, JSON pipes
Why a rewrite is slower
At a global entertainment company and United, we learned that keeping the feedback loop (telemetry + production metrics) is faster. You stabilize in-place, prove deltas, then refactor safely. That’s the play here.
Rewrites reset institutional knowledge
You lose the feedback loop of prod telemetry
The Interventions: Signals, SignalStore, Nx, and Typed Events
1) Baseline and guardrails
We captured before/after metrics per route. Sentry issues were tagged by feature module so noisy areas couldn’t hide.
Angular DevTools profiles stored with commits
Lighthouse + Core Web Vitals in CI
Sentry + OpenTelemetry tied to release SHA
2) Create boundaries with Nx
Nx let us codify architecture: features only call data-access; data-access talks to transport; core owns tokens and guards. Developers got clear extension points without touching other features.
apps/analytics-shell
libs/feature/, libs/ui/, libs/data-access/, libs/core/
ESLint circular-deps + banned-imports rules
3) Replace Subject soup with SignalStore slices
We didn’t rip out every observable—streams are great. We wrapped them with deterministic Signals for rendering and used SignalStore for mutations. This cut re-renders and made SSR-friendly testing straightforward.
SessionStore, FiltersStore, RealtimeStore
Selectors derived with computed()
Effects wrap RxJS with typed adapters
4) Rendering fixes: OnPush, trackBy, virtualization
Tables stopped re-sorting mid-scroll. D3/Highcharts updates moved to animation frames and diffed data, not full redraws.
PrimeNG datatable virtualScroll + rowHeight
ChangeDetectionStrategy.OnPush everywhere
untracked() for heavy computed sections
5) Typed events + resilient transport
Typed event contracts prevented accidental payload drift. Backoff logic stabilized reconnect storms seen during deploys.
WebSocket TTC schema with zod/io-ts
Exponential backoff with jitter
switchMap to cancel stale requests
6) Tests and CI that don’t freeze delivery
We added just enough coverage to stop Friday hotfixes: login, filters, CSV export, and infinite scroll. Lint/test/build ran on affected projects only to keep PRs snappy.
Karma/Jasmine unit tests kept
Cypress smoke per critical user journey
GitHub Actions cache + affected builds
7) Accessibility and UX polish
We met AA without slowing the team. Tokens were checked into libs/ui/theme and consumed via CSS vars for consistency.
PrimeNG tokens for density/contrast
Focus management on live updates
Keyboard nav on data grids
Code Walkthrough: SignalStore Slice, Typed Events, and CI Sanity
// libs/feature/filters/src/lib/filters.store.ts
import { SignalStore, withState, withComputed, withMethods } from '@ngrx/signals';
import { computed, signal, untracked } from '@angular/core';
interface FiltersState {
channelId: string | null;
dateRange: { start: number; end: number };
search: string;
}
const initialState: FiltersState = {
channelId: null,
dateRange: { start: Date.now() - 86400000, end: Date.now() },
search: '',
};
export const FiltersStore = SignalStore(
{ providedIn: 'root' },
withState(initialState),
withComputed((state) => ({
query: computed(() => ({
channelId: state.channelId(),
start: state.dateRange().start,
end: state.dateRange().end,
q: state.search().trim(),
})),
isActive: computed(() => !!state.channelId() || !!state.search().trim()),
})),
withMethods((state) => ({
setChannel(id: string | null) { state.channelId.set(id); },
setRange(start: number, end: number) { state.dateRange.set({ start, end }); },
setSearch(q: string) { state.search.set(q); },
reset() { state.set(initialState); },
}))
);// libs/data-access/realtime/src/lib/realtime.service.ts
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
import { EMPTY, timer } from 'rxjs';
import { retryWhen, mergeMap } from 'rxjs/operators';
import { z } from 'zod';
const MetricEvent = z.object({
type: z.literal('metric'),
channelId: z.string(),
ts: z.number(),
payload: z.object({
impressions: z.number(),
errors: z.number(),
cpu: z.number()
})
});
export type MetricEvent = z.infer<typeof MetricEvent>;
export class RealtimeService {
private socket!: WebSocketSubject<unknown>;
connect(url: string) {
this.socket = webSocket(url);
return this.socket.pipe(
mergeMap((msg) => {
const parsed = MetricEvent.safeParse(msg);
return parsed.success ? [parsed.data] : EMPTY;
}),
retryWhen((errors) =>
errors.pipe(
// Exponential backoff with jitter up to 30s
mergeMap((err, i) => timer(Math.min(30000, (2 ** i) * 1000 + Math.random() * 250)))
)
)
);
}
}# .github/workflows/ci.yml
name: ci
on: [pull_request]
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx nx print-affected --base=origin/main --head=HEAD
- run: npx nx affected -t lint,test,build --parallel=3 --base=origin/main --head=HEADSignalStore for dashboard filters
This slice unified query params, user preferences, and real-time constraints without Subject churn.
Typed WebSocket events with exponential backoff
We enforce payload shape and make reconnection logic explicit and testable.
CI: affected builds with guards
Small, reliable checks on every PR keep rescue work sustainable.
Measurable Results: Before → After
Performance and reliability
We turned off the perpetual fire alarm. PrimeNG virtual scroll + Signals-based rendering removed reflow storms. Typed events killed a whole class of payload bugs.
INP: 380 ms → 145 ms
Largest Contentful Paint: 3.1 s → 1.7 s
Client CPU during bursts: ~90% → ~45%
Sentry error rate: -72% across top 5 issues
Team velocity and maintainability
Nx boundaries and small SignalStore slices gave the team safe places to refactor without breaking neighbors. This mirrors what we’ve done for gitPlumbers (99.98% uptime while modernizing) and IntegrityLens (12k+ interviews processed).
Hotfixes: weekly → < monthly
PR lead time: 2.4 days → 1.1 days
Coverage: 38% → 62% where it matters (critical paths)
When to Hire an Angular Developer for Legacy Rescue
Good signals you’re ready
If this sounds familiar, you don’t need a rewrite—you need architecture and measurement. A senior Angular engineer can stabilize in weeks, not quarters.
Janky tables/charts during bursts
Competing state patterns and Subject churn
QA finds regressions outside the change area
Typical timeline
Most rescues fit in 2–4 weeks without freezing feature work. Discovery call within 48 hours; assessment in 1 week.
Days 1–5: Baseline + Nx boundaries
Weeks 2–3: SignalStore slices + rendering fixes
Weeks 3–4: Transport hardening + CI + a11y
How an Angular Consultant Approaches Signals Migration in a Rescue
Principles I follow
At a global entertainment company we learned to avoid big-bang changes. at a major airline’s kiosks we proved every offline/online flow with Docker-based hardware simulation before rollout. Same discipline applies here.
Touch the hot paths first
Wrap existing streams instead of ripping them out
Prove improvements with telemetry before/after
Tooling I bring
If you need an Angular expert who can thread performance, UX polish, and CI, that’s my lane.
Angular 20+, TypeScript strict, RxJS 7
SignalStore, NgRx where appropriate
PrimeNG, Angular Material, D3/Highcharts
Firebase Hosting/Remote Config, Sentry, OpenTelemetry
Nx, GitHub Actions, Cypress
Key takeaways
- You rarely need a rewrite—guardrails, Signals/SignalStore, and Nx boundaries stabilize fast.
- Instrument first: Angular DevTools, Chrome flame charts, Sentry + OpenTelemetry reveal the real bottlenecks.
- Typed event schemas and a WebSocket pipeline prevent regressions and race conditions.
- Feature-based SignalStores reduce component churn and remove Subject spaghetti.
- Measure outcomes: Core Web Vitals, INP, CPU time, error rates, and deploy frequency.
Implementation checklist
- Establish baselines: Lighthouse, Angular DevTools, INP, error rate, cold-start time.
- Create Nx libs: core, data-access, feature, ui; enforce boundaries via ESLint.
- Introduce SignalStore slices for filters/session/user; remove global mutable state.
- Virtualize tables/charts; OnPush + trackBy + untracked computations.
- Typed WebSocket events + exponential backoff; cancel stale requests.
- Add Cypress smoke + accessibility checks; keep unit tests green (Karma/Jasmine).
- CI guardrails: lint/test/build on PR; feature flags via Firebase Remote Config.
- Sentry + OpenTelemetry for errors and traces tied to commits/deploys.
Questions we hear from teams
- How much does it cost to hire an Angular developer for a rescue?
- Most rescues run 2–4 weeks. Fixed-scope assessments start with a 1-week discovery and baseline. Pricing depends on team size and risk, but it’s typically far less than a rewrite and begins producing value in days.
- How long does an Angular stabilization take?
- A focused rescue is 2–4 weeks: baseline and Nx boundaries in week 1, Signals/SignalStore and rendering fixes in weeks 2–3, and transport hardening plus CI/a11y in week 3–4—without freezing feature work.
- Do we need to migrate everything to Signals?
- No. Keep streams where they shine. We wrap them with typed adapters and use Signals for rendering and local state via SignalStore. This delivers deterministic UI and simpler tests without losing RxJS power.
- What’s involved in a typical Angular engagement?
- Discovery call, codebase assessment with metrics, prioritized intervention plan, implementation with PRs and coaching, and a measured after-state. We leave you with dashboards, guardrails, and clear next steps.
- Can you work with our stack (PrimeNG, Firebase, .NET APIs)?
- Yes. I routinely integrate PrimeNG and Angular Material, ship SSR on Firebase or cloud providers, and connect Node.js or .NET backends. I’ve done this at scale for a global entertainment company, a broadcast media network, United, Charter, an insurance technology company, and an enterprise IoT hardware company.
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