Frameworks

Migrating a Legacy Angular App: A Practical Playbook

3 min read

Every enterprise has one: the Angular app four majors behind, written when NgModules were mandatory, quietly running something the business depends on. Eventually security scanning, a dead dependency, or a hiring problem forces the question. I've run this migration on enterprise commerce codebases, and the playbook below is what actually works — including the unglamorous parts most write-ups skip.

Why this isn't a weekend project

Version-migrating Angular itself is the easy part — Google ships automated schematics and ng update does real work. What makes legacy migrations hard is everything around the framework: third-party libraries that died two majors ago, RxJS operators renamed across versions, TypeScript getting stricter as you climb, custom webpack hacks that fight the modern builder, and — always — the absence of tests around the code you're about to disturb. Budget accordingly: the framework upgrade is a third of the work; dependency archaeology and safety-netting are the rest.

Rule 1: One major version at a time

The temptation is to jump from 12 to 17 in one heroic branch. Don't. Angular's update schematics are designed and tested for adjacent majors — they rewrite your code for each version's breaking changes in sequence, and skipping steps abandons that automation. The version-by-version path also localizes failure: when the app breaks on the 14→15 step, you have one changelog to read, not five stacked ones.

# The loop, per major version:
ng update @angular/core@13 @angular/cli@13
# fix, build, test, commit — then:
ng update @angular/core@14 @angular/cli@14
# ...repeat

Between steps, run the deprecation report (ng update lists them) and clear warnings before they become errors in the next major. Each completed step is a commit and a deployable state — if the timeline gets cut, you've still banked progress.

Rule 2: Buy test coverage before touching old code

Legacy code's defining feature isn't age; it's that nobody's sure what it does. Refactor without a safety net and every change is a coin flip you can't verify. But writing unit tests for code you're about to transform is wasted motion — write characterization tests at the seams instead: a handful of end-to-end tests (Cypress/Playwright) that pin down the flows the business actually cares about. On commerce work that meant: browse → product page → cart → checkout, plus login and search. A dozen E2E tests that assert "the money paths still work" are worth more during migration than five hundred unit tests, because they survive the refactoring that unit tests don't.

Rule 3: Set the timeline with stakeholders like an adult

Migrations die politically before they die technically. The framing that works: capacity percentage, not end date. "Twenty percent of team capacity until done, feature work continues" survives contact with reality; "done by end of Q2" doesn't, because migrations surface surprises by design. Report progress in versions climbed and deprecations cleared — visible, incremental, boring. Executives fund boring.

Also negotiate what doesn't get fixed: a migration is not a redesign, not a state- management rewrite, and not a good time to also adopt the new control-flow syntax everywhere. Park those. Scope creep is how six-week migrations become six-month ones.

A rough phase structure that worked

  • Phase 0 — Recon (a week). Dependency audit: which libraries block which Angular versions? Which are dead and need replacement? This list is your risk profile.
  • Phase 1 — Safety net (1–2 weeks). Characterization E2E tests on the money paths; CI green; deployment rehearsed.
  • Phase 2 — The climb (bulk of the time). One major per iteration: ng update, fix, replace blocked dependencies as they block, run the E2E suite, commit, deploy to staging. Repeat.
  • Phase 3 — Modernize deliberately (ongoing, post-migration). Now adopt standalone components, signals, the new control flow — incrementally, in code you touch anyway. (Strict mode, if you're not there yet, slots in the same way.)

The quiet payoff

The week after our final version bump: build times noticeably down, a hiring req that stopped scaring candidates, security scanning finally green, and — the part nobody predicted — velocity up, because developers stopped routing around the scary parts of the codebase. Legacy migration is unglamorous exactly until it's done. Then it's the best thing the team shipped that quarter.

Found this useful?

Share

I post shorter takes on frameworks & architecture and frontend leadership on LinkedIn — follow along there, or get in touch if you're working on something related.