We speak in hours, not days. Small changes and fixes ship the same day; first builds in weeks, not quarters.

Message us
AngularLegacy modernisationFrontendMigration

AngularJS to Angular Migration: A Step-by-Step Plan That Keeps the Product Running

How to move a live AngularJS 1.x application to modern Angular without a rewrite freeze: audit, preparation, the hybrid ngUpgrade bridge, routing, testing, and when a clean rewrite is the better call.

WeBuildStack engineering team 11 min read

AngularJS reached the end of its road when Google discontinued long-term support on 31 December 2021. Several years on, a surprising number of business applications still run on it: internal portals, insurance back-offices, logistics dashboards and admin consoles that were built well, worked, and never justified a rewrite. We inherit these regularly through our Angular practice, and the question is always the same: how do we get to a supported framework without stopping the business for six months?

This is the plan we use. It is incremental by default, keeps the product shippable at every step, and is honest about the point where a clean rewrite is cheaper than a migration.

Why it has to happen

Three forces push the decision, in roughly this order of urgency.

Security. The AngularJS package on npm is deprecated and receives no fixes from the framework team. Vulnerabilities reported since 2022 stay open in the public builds, and your dependency scanner will keep flagging them. Paid extended support from vendors such as XLTS.dev exists, and we recommend it as a bridge while you migrate, not as a destination.

Toolchain drift. AngularJS predates ES modules, TypeScript as a default, and the current generation of bundlers. Every year, a few more of the libraries, build tools and browser features your team wants will not work cleanly with it. The cost shows up as days lost to compatibility hacks rather than one big failure.

Hiring. Engineers who want to work on AngularJS are increasingly hard to find, and the ones you have would rather not. A migration is as much a retention decision as a technical one.

Step 0: decide migrate versus rewrite

Before the plan, the fork. The hybrid migration described below is designed for applications that are large, in daily use, and still changing. For those, running both frameworks side by side is the only way to keep shipping. But it carries overhead: two frameworks in the bundle, two mental models in code review, and a bridge layer to maintain until the end.

A rewrite is the better call when one or more of these is true:

  • The application is small enough that a team can reproduce it in a handful of sprints.
  • The UI is being redesigned anyway, so the templates would not survive a migration.
  • The codebase relies heavily on $scope inheritance, jQuery DOM manipulation and global state, so you would be preserving the problems rather than the code.
  • There is no automated test coverage and little appetite to add it to the old code.

We make this decision at the end of step 1, with numbers. It is the single most consequential choice in the whole programme, and it should be written down with its reasoning.

Step 1: audit the codebase

A migration estimate without an audit is a guess. Ours takes a tech lead a few days on a mid-sized application and produces a one-page report. What we inventory:

  • Version and shape. AngularJS 1.8 is the final release; anything earlier needs an upgrade first, inside the old framework, because the hybrid bridge targets the 1.5+ component API. Count controllers, directives, components, services, filters and routes.
  • Component readiness. How much of the UI already uses .component() with one-way bindings and controllerAs, and how much is $scope-bound controllers and link-function directives. This ratio, more than line count, predicts effort.
  • Third-party directives. Date pickers, grids, charts and modals written for AngularJS have no Angular equivalent that drops in. Each one becomes a decision: wrap it, replace it, or rebuild it.
  • Routing. ngRoute, ui-router, or custom. ui-router has a supported hybrid path; the others need more care.
  • Build and module system. Script tags and globals, Browserify, webpack, or a gulp pipeline that nobody dares touch. The migration will replace this with the Angular CLI, and the earlier it happens the better.
  • Tests. Karma and Jasmine coverage, Protractor end-to-end suites (Protractor itself is retired), and what actually runs in CI today.
  • Shared state. $rootScope events, service singletons used as global stores, and anything reaching into the DOM.

The output is a table of modules ranked by business value and migration difficulty. That table is the backlog for everything that follows.

Step 2: prepare the AngularJS application

The best migration work happens before Angular appears. These steps improve the old application on their own, and each one makes the hybrid phase cheaper. The Angular team’s own upgrade guide describes them as preparation, and we treat them as a release in their own right.

  1. Follow the component style. Convert $scope controllers to .component() definitions with one-way bindings and controllerAs. This is the single highest-leverage change: an AngularJS component maps almost one-to-one onto an Angular component, while a scope-bound controller maps onto nothing.
  2. One file per unit, ES modules. Split monolithic files so that each component, service or filter is a module that imports its dependencies. This makes the later TypeScript conversion and lazy loading possible.
  3. Introduce TypeScript. Rename files to .ts, start with any where needed, and tighten over time. Type definitions for AngularJS exist on DefinitelyTyped, so the compiler can help even before Angular arrives.
  4. Adopt a modern bundler. Move the build onto the Angular CLI’s toolchain as early as possible, even while the app is still AngularJS. One pipeline for both frameworks removes a whole class of integration problems.
  5. Remove DOM manipulation from controllers. Anything using jQuery or direct DOM access inside business logic has to move into directives or, better, disappear.
  6. Cover the critical paths with tests. Not everything; the flows that make money or that break silently. End-to-end tests written against the real UI survive the migration and tell you when a converted view has regressed.

Teams sometimes resist spending budget on “the old app”. The reframe that works: this is the first third of the migration, done in a form that ships value every sprint.

Step 3: bootstrap the hybrid application with ngUpgrade

Angular’s @angular/upgrade package provides UpgradeModule, which runs an AngularJS application inside an Angular one. The Angular app bootstraps first, then bootstraps AngularJS on the same root element, and the two frameworks share a dependency-injection boundary and run change detection in step.

The mechanics, in order:

  • Create a fresh Angular workspace with the CLI and move the AngularJS source into it, so both live in one repository with one build.
  • Add an Angular AppModule that imports UpgradeModule, and in its ngDoBootstrap call upgrade.bootstrap(document.body, ['yourAngularJsModule']).
  • Verify the application behaves exactly as before. Nothing has been migrated yet; this release is “same product, new bootstrap”. Ship it.

From this point, every AngularJS service can be made available to Angular with a small downgradeInjectable-style factory, and every Angular component can be used in an AngularJS template through downgradeComponent. The bridge works in both directions, which is what lets the migration proceed leaf-first.

A note on bundle size: during the hybrid phase you ship both frameworks. Lazy-load the Angular feature areas as they appear so the extra cost is paid only where it is used, and set a budget in the CLI configuration so regressions are caught in CI.

Step 4: migrate leaf-first, one feature at a time

The order matters. Convert things with the fewest dependants first, so that each converted piece is used by code that does not yet need to change.

  1. Services first. Pure services (API clients, formatters, validation) convert to injectable Angular classes with little ceremony. Register each one with both injectors so existing AngularJS code keeps working.
  2. Leaf components next. Presentational components with inputs and outputs and no children are the easiest conversions. Downgrade each one so its existing AngularJS parent can keep rendering it.
  3. Then container components, walking up the tree. Each converted container brings its already-converted children into the Angular world and removes a bridge.
  4. Filters become pipes, usually mechanically.
  5. Third-party directives are handled by the decision made in the audit: wrap behind an Angular component, replace with an Angular-native library, or rebuild.

New features during this period are written in Angular only. That is a rule, not a preference: it stops the AngularJS surface growing while you are shrinking it. Modern Angular makes this pleasant; standalone components are the default and signals give a reactivity model that is simpler to reason about than $scope digests ever were, so new code written now will not need a second migration later.

For very large applications there is a variant worth knowing: instead of upgrading Angular components into AngularJS, package new Angular features as Angular elements (custom elements) and drop them into the old templates. This removes the tight coupling of the shared injector at the cost of a clearer boundary, and it suits teams that want the two codebases to evolve independently until the switch-over. This is the strangler fig pattern applied to a front end.

Step 5: routing

Routing is where hybrid migrations stall, because both frameworks want to own the URL.

If the application uses ui-router, the maintainers provide @uirouter/angular-hybrid, which lets ui-router manage states that render either AngularJS or Angular views. This is the smoothest path: routing stays as it is, and views switch framework one at a time.

If the application uses ngRoute, the usual approach is to keep ngRoute in charge during the hybrid phase and introduce the Angular Router only once a majority of views are Angular, at which point you switch in one release. Trying to run both routers simultaneously is possible, but it produces the subtle bugs (double navigation, lost query parameters, broken back button) that erode trust in the migration.

Either way, treat the final router switch as its own milestone with its own regression pass over every route, redirect and deep link.

Step 6: testing throughout

The hybrid phase is exactly when regressions slip in, so the test strategy has to be settled before step 3.

  • End-to-end tests are the safety net. Protractor is retired, so move the critical-path suite to Playwright or Cypress during preparation; those tests do not care which framework rendered the page and will outlive the migration.
  • Unit tests travel with the code. Karma is deprecated by the Angular team; new Angular tests should target the CLI’s current test runner so the unit suite is not migrated twice. Keep the old Jasmine suite running for untouched AngularJS code until that code is gone.
  • Visual checks on the converted screens catch the styling differences that come from leaving AngularJS’s class and attribute conventions behind.

Keep both suites green in CI for the whole programme. A migration that disables tests “temporarily” is a rewrite in disguise.

Step 7: remove the bridge

When the last AngularJS component is gone, the finish is mechanical but worth doing properly:

  • Remove UpgradeModule, the AngularJS bootstrap, and every downgrade registration.
  • Remove angular, angular-* and the hybrid router packages from package.json.
  • Delete the AngularJS test configuration.
  • Re-measure the bundle; the drop is usually large and is a good number to report to whoever funded the work.
  • Run the CLI’s update guide to bring Angular to the current major version if the migration spanned a release.

Then write down what you learned. The next modernisation on your roadmap, and there will be one, benefits from a short honest record of what took longer than planned.

How long it takes, and how to run it

We do not publish a formula, because the honest answer from the audit is “it depends on the ratio of components to scopes and on how many third-party directives you have”. What we can say is how to run it so the estimate holds:

  • Run it as a stream alongside feature work, not as a freeze. A small dedicated pod (often two engineers and a tech lead reviewing) migrating leaf-first makes steady, visible progress without blocking the product team.
  • Ship on every sprint. Each converted feature goes to production behind the bridge, so there is no big-bang release at the end.
  • Track a single number weekly: the count of AngularJS components remaining. When it is visible, it goes down.

Where to start today

If you are reading this with a live AngularJS application, the first three actions are cheap and reversible: upgrade to AngularJS 1.8 if you are behind, arrange extended support for the duration, and commission the audit. The audit turns a vague worry into a ranked backlog and a migrate-or-rewrite decision you can defend.

We run these audits as part of our Angular development work, and the project scoper will give you a sense of team shape for either path. If you have a migration under way and it has stalled, the usual culprits are routing and untested shared state; both are fixable without starting over.

FAQ

Questions this guide gets asked

Something not covered here? Ask a tech lead. We reply within one business day.

No. Google ended long-term support for AngularJS on 31 December 2021. Security fixes come only from paid third-party extended-support vendors, and new browser, build-tool and dependency versions are not tested against it.

It depends on the size and health of the codebase, not the calendar. A small, well-structured 1.x app with components and a module loader can move in a few sprints; a large app built on scopes and jQuery plugins is a multi-month programme run alongside feature work. The audit in step 1 of this guide is what turns that into a real estimate.

Migrate incrementally when the application is large, in daily use and still changing, because the hybrid approach lets you ship throughout. Rewrite when the app is small, when the UI is being redesigned anyway, or when the codebase is in such poor shape that you would be preserving problems rather than code.

ngUpgrade is Angular's official bridge for running AngularJS and Angular in the same page. It lets Angular components be used inside AngularJS templates (and the reverse), shares dependency-injection services across both frameworks, and keeps change detection in step so you can convert the application one piece at a time.

Yes. The ui-router team publishes an angular-hybrid package that lets ui-router handle routing across both AngularJS and Angular views during the hybrid phase. Moving to the Angular Router is a later step, done once most views are Angular.

Next step

Want a second opinion on your project?

Send us a few lines. A tech lead will give you an honest recommendation, even if it is not us.

  • We speak in hours, not days
  • Written quote within 48 hours
  • Deployment included, you own the code