Zero‑Downtime Angular 11→20 Upgrade for a Telecom Analytics Dashboard: Timeline, Breaking Changes, and Measurable Wins

Zero‑Downtime Angular 11→20 Upgrade for a Telecom Analytics Dashboard: Timeline, Breaking Changes, and Measurable Wins

How we moved a Fortune‑100 analytics app from Angular 11 to 20 without a single minute of downtime—and finished with faster builds, smaller bundles, and better UX metrics.

“Zero downtime isn’t luck—it’s guardrails, canaries, and measuring what matters.”
Back to all posts

I’ve sat in too many 7 a.m. war rooms where an analytics dashboard jitters under load while sales is trying to hit their number. This case study is one of the good mornings: a leading telecom provider’s Angular 11 app moved to Angular 20 with zero downtime, then shipped faster and felt lighter—without a rewrite.

Context: multi‑tenant analytics with role‑based reporting, PrimeNG heavy UI, Highcharts, and a fair bit of NgRx. Infra ran in an Nx monorepo with GitHub Actions and Firebase Hosting previews. I led the upgrade as a remote Angular consultant, coordinating with platform and QA.

The playbook below is the same one I’d bring to your team if you need to hire an Angular developer to rescue a legacy app, stabilize production, and get you to Angular 20+ safely.

The Upgrade Scene: What Had to Stay Up

As enterprises plan 2025 Angular roadmaps, expecting zero downtime isn’t a nice‑to‑have—it’s table stakes. The upgrade had three phases: assessment, incremental version hops, and performance passes with canary rollout.

Constraints

We ran blue/green style deploys and canary routes. Canary users were opted in via a cookie header; everyone else stayed on the stable green environment. That gave us instant rollback but, more importantly, instant roll‑forward when confidence grew.

  • Zero downtime—regulatory reporting windows could not slip.

  • No visual regressions in core dashboards.

  • Keep NgRx for now; incremental Signals adoption only in hot paths.

  • Support SSO and existing interceptors; no auth flow churn.

Timeline: 4 Weeks to Angular 20 with Canaries

Below is the exact command spine we used. The codebase was Nx, but the commands are framework‑standard.

Week 0 — Assessment + Guardrails

We recorded LCP/INP/TBT and error rate per route. That baseline is how you prove the upgrade helped, not just “didn’t break.”

  • Angular DevTools profile to find hot components.

  • Baseline metrics: Lighthouse CI, GA4 INP, Firebase Performance Monitoring.

  • CI safety: Cypress smoke suite, bundle budgets, type checking strict.

Week 1 — 11→13→15 Hops

We kept RxJS on 7.8 to minimize risk. Protractor projects were replaced with Cypress. Typed forms surfaced a handful of control mismatches early—exactly where we wanted to find them.

  • ng update @angular/core@13 @angular/cli@13, then to 15.

  • Migrate TSLint remnants to ESLint.

  • Start typed forms migration (Angular 14+).

Week 2 — 15→17 + Builder Change

This was the spiciest week: deleting custom webpack configs and migrating to the Angular Vite builder cut build times ~40% immediately.

  • Switch to Angular’s Vite builder introduced in 17+ (now default by 20).

  • Remove webpack‑specific customizations and obsolete polyfills.

  • Enable deferrable views for non‑critical routes.

Week 3 — Library Upgrades + UI Safety

We used CI screenshots and a11y checks to guarantee no regressions in dense data tables. Design tokens insulated minor UI shifts.

  • PrimeNG 17 upgrade with visual diff checks.

  • Angular Material MDC migration for a small admin area.

  • Tokenize spacing/color to absorb theme shifts.

Week 4 — 17→20, Canaries, and Rollout

We kept a hot rollback and a feature flag to disable deferrable views if hydration logs spiked. They didn’t—we rolled forward in under two days.

  • ng update @angular/core@20 @angular/cli@20.

  • Ship v20 to a 10% canary via header routing; watch errors and UX metrics.

  • Increase to 50%, then 100% over 48 hours.

Upgrade Commands and Config Spine

# conservative hops; fix tests after each
npx nx migrate @angular/cli@13 @angular/core@13
npx nx migrate --run-migrations

npx nx migrate @angular/cli@15 @angular/core@15
npx nx migrate --run-migrations

# Move to 17 for new builder + deferrable views
npx nx migrate @angular/cli@17 @angular/core@17
npx nx migrate --run-migrations

# Final hop to 20
npx nx migrate @angular/cli@20 @angular/core@20
npx nx migrate --run-migrations

# Keep RxJS at 7.8 initially; schedule RxJS 8 later with a compat layer
// angular.json (excerpt) — switch to Vite builder in Angular 17+
{
  "projects": {
    "dashboard": {
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "builder": "@angular-devkit/build-angular:application" // Vite-based
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server"
        }
      }
    }
  }
}

CLI and Framework Hops

Switch to Vite Builder

Breaking Changes We Actually Hit—and How We Fixed Them

These are the changes teams actually feel. Typed forms and builder migrations surfaced the most code touches; UI library upgrades needed a UX safety net; and a few surgical Signals swaps paid off immediately.

Typed Forms (14+)

// Before (Angular 11)
const form = this.fb.group({ count: [0], email: [''] });

// After (typed forms)
interface FiltersForm {
  count: FormControl<number>;
  email: FormControl<string>;
}

const form = new FormGroup<FiltersForm>({
  count: new FormControl(0, { nonNullable: true }),
  email: new FormControl('', { nonNullable: true })
});

  • Fix FormGroup/Control generics.

  • Convert any to explicit domain types.

  • Update custom validators with correct signatures.

HTTP Interceptors Tightened Types

@Injectable({ providedIn: 'root' })
export class AuthInterceptor implements HttpInterceptor {
  constructor(private token: TokenService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authReq = req.clone({ setHeaders: { Authorization: this.token.bearer() }});
    return next.handle(authReq).pipe(
      catchError(err => err.status === 401 ? this.token.refresh().pipe(
        exhaustMap(() => next.handle(req.clone({ setHeaders: { Authorization: this.token.bearer() }})))
      ) : throwError(() => err))
    );
  }
}

  • Align HttpEvent typing and clone usage.

  • Centralize 401 refresh with typed exhaustMap.

Builder & Polyfills

Angular 20’s toolchain with the Vite builder and modern browsers made our legacy polyfills unnecessary. Bundle size dropped immediately.

  • Remove custom webpack configs and differential loading.

  • Trim polyfills now covered by modern targets.

Material MDC & PrimeNG 17

We leaned on CI visual diffs and design tokens to avoid “surprise” UI shifts—a common fear on exec calls.

  • Adjust density and tokens to match legacy look.

  • Swap deprecated selectors; fix icon baselines.

Zone‑heavy Patterns in Hot Components

We didn’t rewrite state; we targeted the top three offenders the Angular DevTools flame chart called out. This was enough to meaningfully move INP.

  • Replace chatty inputs with Signals in key filters.

  • Use runOutsideAngular for long‑running streams.

Zero‑Downtime Release: Canary Strategy

Blue/green isn’t new, but pairing it with precise UX metrics gives product leaders confidence to push the throttle. No late‑night heroics required.

Routing Canaries with Headers

# firebase.json (excerpt) — canary via cookie header
hosting:
  rewrites:
    - source: "**"
      function: ssrApp
  headers:
    - source: "**"
      headers:
        - key: "Cache-Control"
          value: "public, max-age=300"
# Cloud Function inspects cookie and serves v20 or stable

  • Cookie signals a “canary” user.

  • Edge rewrites send canary traffic to v20 bundle.

Feature Flags

Firebase Remote Config let us swap UX behavior without a new deploy. When your CFO is watching, that flexibility matters.

  • Remote flag to toggle deferrable views.

  • Flag for Signals‑based filters vs legacy inputs.

Rollforward > Rollback

We wired GA4 INP and Firebase Perf thresholds into Slack. If error rate or INP spiked, we could shift traffic in under a minute.

  • Keep both stacks hot for 48 hours.

  • Automate error and UX metric alerts.

Performance Improvements (with Real Numbers)

Anecdotes don’t close budgets—numbers do. These are Lighthouse, GA4, and Firebase Performance stats pulled during the rollout window.

Bundle & Build

Switching to the Vite builder and deleting polyfills + differential loading did the heavy lifting. Dynamic importing Highcharts for non‑default routes finished the job.

  • Bundle size: −23% main, −38% initial JS for charts route.

  • Build time: −42% CI cold build; −70% with Nx caching.

Interaction & Hydration

Two hot filter components were converted to Signals. We replaced Input()/Output() chatter with computed() and a small SignalStore for derived state. No rewrite—just fewer change detection passes.

  • INP median: 180ms → 120ms on filters‑heavy pages.

  • Hydration cost: −18% on dashboards with deferrable views.

Stability

Typed forms killed several undefined issues in rarely tested branches. Canary routing avoided thundering‑herd deploy mistakes.

  • Error rate: −36% (typed forms and interceptor fixes).

  • Uptime during rollout: 100% (no 5xx bursts).

How an Angular Consultant Approaches Signals Migration

Signals are a lever, not a religion. In upgrades, I apply them surgically to move INP and CPU time where it matters. If you need a senior Angular engineer to do this safely, I’m available for hire.

Targeted, Not Total

// A tiny SignalStore for a filter panel
import { signal, computed } from '@angular/core';

class FiltersStore {
  private _count = signal(0);
  private _email = signal('');
  readonly valid = computed(() => this._count() >= 0 && this._email().includes('@'));
  setCount(v: number) { this._count.set(v); }
  setEmail(v: string) { this._email.set(v); }
}

  • Instrument first, migrate second.

  • Convert the 20% of components causing 80% of churn.

Interop with RxJS

We kept RxJS at 7.8 and derived signals from selectors in the hot path. RxJS 8 came later behind a feature branch and compat helpers.

  • Keep NgRx; derive signals from selectors.

  • Don’t chase RxJS 8 on day one of an Angular upgrade.

When to Hire an Angular Developer for Legacy Rescue

I’ve upgraded airport kiosk apps under offline constraints, media schedulers with complex timelines, and insurance telematics dashboards with real‑time telemetry. The playbook is repeatable and safe when done with guardrails.

Signs You’re Ready

If your roadmap assumes “we’ll upgrade later,” production risk is compounding. A focused 4–8 week engagement pays back quickly—often before quarter‑end.

  • Angular <14 with untyped forms and custom webpack.

  • High CI flake rate; no bundle budgets.

  • Feature work blocked by outdated dependencies.

What to Instrument Next

Keep momentum. Upgrades unlock velocity only if you keep the guardrails and telemetry in place.

Post‑Upgrade Investments

We typically line up a follow‑on sprint to modernize state slices to SignalStore, enable SSR if needed, and expand our performance budgets to cover new routes.

  • Visual regression in CI for every UI library bump.

  • SSR hydration metrics if SEO matters.

  • Schedule RxJS 8 and progressive standalone adoption.

Related Resources

Key takeaways

  • Plan upgrades in hops (11→13→15→17→20) to isolate risk and keep prod alive.
  • Run canary releases and feature flags so you can roll forward instantly without downtime.
  • Handle typed forms, builder migration (webpack→Vite), Material MDC, and Node/TS bumps early.
  • Use deferrable views, dynamic imports, and targeted Signals to cut INP and bundle size.
  • Track metrics (Lighthouse, GA4, Firebase Perf) so stakeholders see concrete ROI.

Implementation checklist

  • Baseline production metrics (Lighthouse CI, GA4 INP, Firebase Performance).
  • Create Nx branch and parallel v20 build target with feature flag routing.
  • Upgrade in supported hops with ng update, fix typed forms, interceptors, and tests.
  • Migrate to Angular Vite builder, remove webpack-specific configs and polyfills.
  • Canary release via cookie/header routing; monitor error rate and UX metrics.
  • Cut bundle by deferring charts and heavy libs; add Signals to hot components.
  • Complete rollout; keep rollback plan hot for 48 hours; publish upgrade notes.

Questions we hear from teams

How long does an Angular 11→20 upgrade take?
For large dashboards, plan 4–8 weeks. This case took 4 weeks with a disciplined hop strategy, CI guardrails, and canary rollout. Smaller apps can land in 2–3 weeks if dependencies are healthy.
What does a zero‑downtime strategy look like for Angular?
Run blue/green environments, route a canary cohort via header or cookie, and wire UX/error budgets to alerts. Roll forward as confidence grows. Keep rollback hot for 48 hours post‑cutover.
Do we need to migrate fully to Signals during the upgrade?
No. Target hot components first. Keep NgRx and RxJS 7.8, derive signals where it moves INP. Schedule broader Signals/SignalStore adoption after production is stable.
How much does it cost to hire an Angular consultant for an upgrade?
Budgets vary by scope, but typical engagements run 2–8 weeks. I offer fixed‑price assessments and milestone billing tied to measurable deliverables—tests passing, canary live, metrics improved.
What tools should we add to CI for safe upgrades?
Nx affected builds, Lighthouse CI with budgets, Cypress smoke tests, ESLint strict mode, and bundle size checks. Optional: visual diff tests if you’re upgrading Material or PrimeNG.

Ready to level up your Angular experience?

Let AngularUX review your Signals roadmap, design system, or SSR deployment plan.

Hire Matthew — Remote Angular Expert, Available Now Discuss Your Angular 20+ Roadmap

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
NG Wave Component Library

Related resources