Zero‑Downtime Angular 11 → 20 Upgrade: Timeline, Breaking Changes, and the 68% INP Win (Case Study)

Zero‑Downtime Angular 11 → 20 Upgrade: Timeline, Breaking Changes, and the 68% INP Win (Case Study)

How we upgraded a broadcast scheduling platform from Angular 11 to 20 with blue‑green deploys, feature flags, Signals, and measurable Core Web Vitals gains—without an outage.

“Zero downtime isn’t magic. It’s guardrails + selective modernization + a canary that tells you the truth.”
Back to all posts

I’ve been pulled into enough “don’t break production” upgrades to know the fear is rational. This case study is a real one: we upgraded a broadcast media network’s Angular 11 scheduling platform to Angular 20, live traffic, no downtime. The result: faster UI, fewer errors, and zero pager duty.

If you’re looking to hire an Angular developer or an Angular consultant to run a similar upgrade, here’s the exact timeline, the breaking changes we hit, the guardrails we used, and the measurable outcomes leadership cared about.

The Night We Upgraded Without an Outage

Challenge

The scheduling dashboard had to keep running while we modernized. Editors were slotting promos every minute; a blip would cost inventory. We targeted Angular 20+, RxJS 8, and TypeScript 5 with zero downtime.

  • Angular 11 SPA, 40+ routes, PrimeNG components, heavy RxJS effects

  • Strict uptime windows—traffic never sleeps for a broadcast schedule

Intervention

We split work into platform upgrades and focused UX refactors. The release train never stopped: canary first, ramp when error budgets stayed green.

  • Blue‑green deploys behind the CDN with weighted canary

  • Feature flags for risky UI changes

  • Selective Signals/SignalStore where flame charts showed churn

Result

The upgrade paid for itself in a sprint of saved time per editor per week—plus confidence to ship faster going forward.

  • 99.98% uptime maintained

  • INP improved 68%, LCP improved 38%

  • JS bundle −28%, CPU time −41%, production errors −73%

Why Angular 11 Apps Break During a 20 Upgrade

As companies plan 2025 Angular roadmaps with Angular 21 around the corner, these are still the same places teams trip. The fix: codemods, strictness early, and a canary that proves improvements before you flip 100% traffic.

Common fault lines

Angular 11 era patterns often rely on deprecated APIs. Going straight to 20 without a plan can surface death‑by‑a‑thousand paper cuts. We tackled the noisy ones first to protect the rollout timeline.

  • RxJS 8 import changes and toPromise removal

  • Stricter TypeScript 5 + TSConfig changes

  • Builder shifts (application builder), budgets, optimizations

  • Typed forms and functional router APIs

  • Polyfills/browserslist changes and zone.js differences

Six‑Week Zero‑Downtime Timeline

Week 0–1: Assessment + Guardrails

We instrumented GA4 with CI build metadata to compare before/after per release, then created a dedicated blue bucket with instant rollback.

  • Freeze UX changes; perf + error snapshot (Lighthouse, GA4/BigQuery, Angular DevTools)

  • Cypress smoke tests for critical paths (auth, scheduler CRUD, search)

  • Blue‑green CDN route + rollback plan; feature flag plumbing ready

Week 2–3: Platform Upgrades

We advanced versions in a staging branch, fixing breakage as we went. Each green build deployed to the ‘green’ environment for smoke tests.

  • CLI/builder increments, TypeScript 5, RxJS 8 codemods

  • ESLint migration, tsconfig strict, polyfills cleanup

  • Unit tests stable (Karma/Jasmine) and E2E (Cypress) green on both stacks

Week 4: Signals in Hot Paths

We only touched hotspots the flame charts flagged: schedule grid filters and a couple of PrimeNG tables with expensive pipes.

  • Convert heavy async/inputs to signals/computed

  • SignalStore for session/user preferences and derived selectors

  • Keep surface area small; measure render counts

Week 5: 10% Canary + Ramp

When metrics stayed green, we ramped traffic and locked the win.

  • Weighted routing 10%→50%→100% over 48–72 hours

  • Error budget SLOs: <1% regressions allowed

  • Visual regression checks on critical stories

Week 6: Cleanup + Docs

This is where future you says thanks. We included before/after metrics and snippets in the repo’s /docs.

  • Prune polyfills, delete dead code, document upgrade

  • Create a Signals migration guide for future contributors

Commands and Codemods that De‑Risked the Jump

# Run migrations in a staging branch, validate each step in CI
ng update @angular/core@12 @angular/cli@12 --force
ng update @angular/core@13 @angular/cli@13 --force
ng update @angular/core@14 @angular/cli@14 --force
ng update @angular/core@15 @angular/cli@15 --force
ng update @angular/core@16 @angular/cli@16 --force
ng update @angular/core@17 @angular/cli@17 --force
ng update @angular/core@18 @angular/cli@18 --force
ng update @angular/core@19 @angular/cli@19 --force
ng update @angular/core@20 @angular/cli@20 --force

# Update dependencies commonly pinned to Angular major versions
ng add @angular-eslint/schematics
npm i -D typescript@~5 rxjs@^8

// RxJS before (Angular 11 codebase)
import { map, catchError, switchMap } from 'rxjs/operators';

// After (RxJS 8 pathless imports)
import { map, catchError, switchMap, firstValueFrom } from 'rxjs';

// toPromise removal → firstValueFrom/lastValueFrom
const value = await firstValueFrom(api.getData$());

// Typed forms + nonNullable to avoid undefined traps
import { FormControl, NonNullableFormBuilder } from '@angular/forms';

// before
const title = new FormControl('');

// after
const title2 = new FormControl<string>('', { nonNullable: true });

// or with builder (recommended)
constructor(private fb: NonNullableFormBuilder) {}
form = this.fb.group({ title: [''], duration: [0] });

// Functional router guards (cleaner + tree‑shakable)
import { inject } from '@angular/core';
import { CanActivateFn } from '@angular/router';
import { AuthService } from './auth.service';

export const authGuard: CanActivateFn = () =>
  inject(AuthService).isAuthenticated();

CLI/Builder increments

We moved version by version in CI to surface breakage early.

RxJS 8 imports + toPromise removal

Pathless operator imports and first/lastValueFrom replaced deprecated patterns.

Typed forms + TS 5 strictness

We introduced non‑nullable controls and fixed unsafe unions.

Selective Signals and SignalStore Where It Mattered

import { Component, signal, computed, effect } from '@angular/core';
import { SignalStore, withState, withMethods } from '@ngrx/signals';

interface PrefsState { density: 'comfortable' | 'compact'; start: Date; end: Date; }

export const PrefsStore = SignalStore(
  withState<PrefsState>({ density: 'comfortable', start: new Date(), end: new Date() }),
  withMethods((store) => ({
    setDensity: (d: PrefsState['density']) => store.patch({ density: d }),
    setRange: (start: Date, end: Date) => store.patch({ start, end })
  }))
);

@Component({
  selector: 'ux-schedule-filters',
  template: `
    <p-inputText [(ngModel)]="searchTerm()" (ngModelChange)="onSearch($event)"/>
    <p-dropdown [options]="densities" [ngModel]="prefs.density()" (onChange)="prefs.setDensity($event.value)"></p-dropdown>
    <div>Visible slots: {{ visibleCount() }}</div>
  `
})
export class ScheduleFiltersComponent {
  prefs = new PrefsStore();
  searchTerm = signal('');
  slots = signal([...Array(1000)].map((_, i) => ({ id: i, name: `Slot ${i}` })));
  visible = computed(() => this.slots().filter(s => s.name.includes(this.searchTerm())));
  visibleCount = computed(() => this.visible().length);

  constructor() {
    effect(() => console.debug('Render visible count', this.visibleCount()));
  }

  onSearch(term: string) { this.searchTerm.set(term); }
}

Why selective?

Angular 20’s Signals are fantastic, but ROI comes from focus. We left most components alone and targeted the schedule grid filters that churned on every keystroke.

  • We don’t rewrite stable code; we target hot paths the profiler flags.

  • Signals win where input churn and async glue caused over‑rendering.

Implementation

This trimmed change detection work and simplified tests.

  • Signals in component state, computed for derived data

  • SignalStore for user prefs, column density, and date ranges

CI/CD Blue‑Green and Canary Rollout

name: deploy-blue-green
on:
  push:
    branches: [ main ]
jobs:
  build-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm ci
      - run: npm run build -- --configuration=production
      - name: Upload to GREEN
        run: aws s3 sync dist/app s3://myapp-green --delete
      - name: Cypress smoke (GREEN)
        run: npx cypress run --env baseUrl=https://green.myapp.example.com
      - name: Shift 10% traffic to GREEN
        run: |
          aws route53 change-resource-record-sets --hosted-zone-id ZONE --change-batch file://weighted-10.json
      - name: Wait and verify metrics
        run: node scripts/check-ga4-error-budget.js
      - name: Shift 100% if healthy
        if: success()
        run: aws route53 change-resource-record-sets --hosted-zone-id ZONE --change-batch file://weighted-100.json

What we shipped

Choose your cloud—Azure/GCP patterns are similar. The key is instant rollback and traffic weighting.

  • Two S3 buckets (blue/green) behind CloudFront; Route53 weighted canary

  • GitHub Actions builds both targets; Cypress smoke before flip

Guardrails

We added a simple automation step: if errors > SLO, abort flip and roll back.

  • GA4 dashboards with CI release tags

  • Error budget SLOs block full rollout automatically

Measurable Outcomes: Core Web Vitals and Runtime

Before vs After (production)

We attribute most gains to Signals in hot paths, RxJS cleanup, and modern builder optimizations in Angular 20. PrimeNG updates plus trimming dead polyfills helped bundle size.

  • INP: 340 ms → 109 ms (−68%)

  • LCP: 3.4 s → 2.1 s (−38%)

  • JS: 1.9 MB → 1.37 MB (−28%)

  • Main‑thread CPU: −41%

  • Errors/session: −73%

How we proved it

Executives saw a Looker Studio report tied to releases. Engineers saw flame charts go from red to green.

  • GA4 + BigQuery with release tags

  • Angular DevTools render counts on critical components

When to Hire an Angular Developer for Legacy Rescue

See how I stabilize chaotic code at gitPlumbers (70% delivery velocity increase, 99.98% uptime) and review my live products: IntegrityLens (12k+ interviews) and SageStepper (+28% score lift across 320 communities).

Signals to call in help

If you need a remote Angular developer with Fortune 100 experience to stabilize and upgrade without freezing delivery, bring in an Angular consultant early. I’ve done this for a major airline (kiosk software with Docker hardware simulation), a global entertainment company (employee tracking/payments), and a leading telecom provider (ads analytics dashboards).

  • Multiple majors behind and uptime is non‑negotiable

  • RxJS/TypeScript debt blocks upgrades

  • CI/CD isn’t blue‑green and rollback isn’t instant

How an Angular Consultant Approaches Signals Migration

// Example: converting a chatty @Input + async pipe to a signal boundary
@Component({ selector: 'ux-row', template: `{{ vm().name }} ({{ vm().status }})` })
export class RowComponent {
  private _vm = signal<{ name: string; status: string }>({ name: '', status: 'idle' });
  vm = computed(() => this._vm());
  @Input() set data(d: { name: string; status: string }) { this._vm.set(d); }
}

Playbook

Signals aren’t a silver bullet. In this upgrade, we touched <10% of components and got the majority of the win. The rest benefited from builder and RxJS changes.

  • Profile first, migrate second

  • Replace async/template churn with signals/computed

  • Use SignalStore for cross‑component state; keep actions typed

  • Measure render counts before/after

Related Resources

Key takeaways

  • Zero‑downtime Angular 11→20 is practical with blue‑green deploys, feature flags, and a canary rollout.
  • Handle RxJS 8 import changes, typed forms, strict TS, and builder shifts early to reduce risk.
  • Selective Signals/SignalStore adoption in hot paths cut render counts and improved INP 68%.
  • Guardrails (Cypress smoke tests, GA4/BigQuery dashboards, error budgets) de‑risk the switch.
  • Measurable outcomes: −28% JS, −41% CPU time, +99.98% uptime maintained during migration.

Implementation checklist

  • Snapshot perf + errors before you touch code (Lighthouse, Angular DevTools, GA4/BigQuery).
  • Lock a blue‑green deployment path with instant rollback (CDN routing or DNS weights).
  • Codemod RxJS imports to pathless and replace toPromise with first/lastValueFrom.
  • Adopt TypeScript 5 strictness and fix any implicit anys + union traps early.
  • Upgrade builder/CLI iteratively; validate chunking and budgets at each step.
  • Introduce Signals/SignalStore only where flame charts show churn—measure before/after.
  • Gate risky UI behind feature flags and run a 10% canary for 24–72 hours.
  • Automate Cypress smoke + visual checks (Storybook/Chromatic optional) in CI.

Questions we hear from teams

How long does an Angular 11 to 20 upgrade take?
For a medium enterprise dashboard (30–60 modules), plan 4–8 weeks. This case took six weeks with blue‑green deploys, feature flags, RxJS/TS strictness, and selective Signals. Smaller apps can go faster; complex monorepos can need more runway.
What does zero‑downtime really mean for Angular upgrades?
Users never hit a 500 or see a maintenance screen. We use blue‑green or weighted DNS, Cypress smoke tests, and feature flags to canary traffic. If error budgets trip, we instantly roll back without redeploying code.
What are the biggest breaking changes from Angular 11 to 20?
RxJS 8 imports and toPromise removal, TypeScript 5 strictness, builder/CLI changes, typed forms, and polyfills/browserslist updates. We also modernize router guards and HttpClient setup to functional providers where it makes sense.
How much does it cost to hire an Angular developer for this work?
Engagements vary by scope, but typical ranges are 2–4 weeks for assessments/rescues and 4–8 weeks for full upgrades. I offer fixed‑fee assessments and milestone‑based delivery for predictable spend.
Do we have to adopt Signals everywhere?
No. We adopt Signals and SignalStore only where flame charts show churn. In this case, <10% of components changed yet INP improved 68%. Keep scope small; measure gains; iterate.

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 See how we rescue chaotic Angular apps (gitPlumbers)

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