
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.jsonWhat 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
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.
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