DEV Community

Cover image for The Pragmatic Migration: Moving from AngularJS to Angular v22
Preston West
Preston West

Posted on • Originally published at linkedin.com

The Pragmatic Migration: Moving from AngularJS to Angular v22

The tech industry is littered with the ghosts of failed migrations—multi-year rewrites that burned through millions in budget only to be scrapped, or catastrophic IT shifts like the British bank TSB's migration that resulted in the corruption of 1.3 billion customer records. For organizations still relying on legacy AngularJS (v1.x), the pressure to modernize is not just a desire for developer convenience—it is a critical business and cybersecurity imperative. AngularJS has been officially in End of Life (EOL) status for years. Every day that passes without security patches and official support introduces massive risk to enterprise systems.

Yet, the idea of refactoring a massive, monolithic enterprise application from the ground up rightfully induces panic in product managers and engineering leaders alike. It is a cliche story, albeit very important: feature development grinds to a halt for months, the business grows frustrated, and by the time the "new" application is finished, enthusiasm for the project has shrunk and requirements have shifted.

But there is a way to thread the needle of ever-changing business requirements and legacy technical debt.

The path to the highly performant, modern Angular v22 (released June 2026) does not require a risky "Big Bang" rewrite. Using a strategic, incremental, hybrid approach, you can migrate your legacy application piece by piece while continuously delivering business value.

In this comprehensive guide, we will walk you through the architectural strategy, the technical steps, and the code required to bridge the gap between AngularJS and Angular v22.

(If you are an engineering leader looking for an expert partner to modernize your frontend stack without all the risk, MigrateNg is our specialized service that handles this entire process for you.)


Why Target Angular v22? The Business and Technical Case

If you are migrating today, you are aiming for Angular v22. This version represents a massive culmination of modern web architecture, shedding the heavy burdens of the past and embracing an incredibly fast, reactive future.

1. Zoneless Architecture

Historically, Angular relied on zone.js to magically intercept asynchronous events (like setTimeout, HTTP requests, and DOM events) and trigger change detection. While this was revolutionary years ago, it introduced massive overhead for large applications. Angular v22 fully embraces a "Zoneless" architecture. By dropping zone.js, applications experience drastically reduced bundle sizes, faster initial load times, and an elimination of unnecessary change detection cycles.

2. First-Class Reactivity with Signals

Angular v22 is a signal-first framework. Signals provide a fine-grained, composable, and predictable model for state management and reactivity. Instead of relying on complex RxJS streams for basic state, or dealing with the unpredictability of AngularJS $watchers, Signals allow the framework to know exactly what data changed and exactly which part of the DOM needs to be updated. Furthermore, v22 brings the stabilization of Signal Forms, which provide a strictly typed, synchronous, and boilerplate-free way to handle complex form states.

3. The Resource API

Handling asynchronous data fetching is notoriously complex. Angular v22 introduces the Resource API (resource() and httpResource()). This provides an elegant, declarative way to handle loading states, error states, and data fetching without manually managing RxJS subscriptions or complex async pipes in your templates.

4. Performance by Default (OnPush)

In legacy applications, performance profiling often revealed that the framework was checking the entire component tree on every single keystroke. In Angular v22, OnPush change detection is the default. The framework will only check a component if its input references change or if a Signal it consumes emits a new value. This provides maximum performance out-of-the-box with zero configuration required.

5. Developer Ergonomics and AI Tooling

With features like template arrow functions, spread syntax in templates, and built-in Model Context Protocol (MCP) integrations for AI coding assistants, Angular v22 offers one of the most productive developer experiences in the ecosystem.


The Anti-Pattern: Why You Shouldn't Rewrite from Scratch

Before diving into the code, we must address the most common mistake organizations make: The Big Bang Rewrite.

When developers look at a sprawling, messy, decade-old AngularJS application, the immediate instinct is to burn it down and start over. Here are some obvious problems with that approach:

  1. The Feature Freeze: The business must stop building new features on the old app while the new app is built. Competitors immediately gain ground.
  2. The Moving Target: While you build the new app, users still need bug fixes in the old app. You are now maintaining two distinct codebases.
  3. The Hidden Requirements: The legacy app contains ten years of undocumented business logic and edge cases. The rewrite inevitably misses these, leading to a disastrous relaunch.

The undisputed industry standard for large enterprise applications is an incremental migration.

(Struggling to convince stakeholders to avoid the Big Bang rewrite? MigrateNg provides expert architectural consulting and execution to ensure a smooth, low-risk transition.)


The Strategic Approach: Hybrid Migration with ngUpgrade

The secret to an incremental migration is running both AngularJS and Angular v22 side-by-side in the exact same browser tab. This is achieved using Angular's @angular/upgrade/static module (commonly referred to as ngUpgrade).

ngUpgrade acts as a bridge. It allows:

  1. Component Interoperability: You can render a modern Angular v22 component inside an old AngularJS template, and vice-versa.
  2. Dependency Injection (DI) Bridging: You can inject a modern Angular service into an AngularJS controller, and vice-versa.

Setting Up the Hybrid Bootstrapper

To start, you must stop bootstrapping your AngularJS app using the ng-app directive in your HTML. Instead, you will bootstrap the modern Angular v22 application, and tell it to boot AngularJS.

// main.ts
import { enableProdMode } from "@angular/core";
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { UpgradeModule } from "@angular/upgrade/static";
import { AppModule } from "./app/app.module";

// 1. Bootstrap the modern Angular v22 Module
platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .then((platformRef) => {
    // 2. Extract the UpgradeModule
    const upgrade = platformRef.injector.get(UpgradeModule);

    // 3. Manually bootstrap the legacy AngularJS application
    // 'myLegacyApp' is the name of your root AngularJS module
    upgrade.bootstrap(document.body, ["myLegacyApp"], { strictDi: true });
  })
  .catch((err) => console.error(err));
Enter fullscreen mode Exit fullscreen mode

Downgrading an Angular v22 Component

Suppose you have written a brand new, highly performant Angular v22 component using Standalone Components and Signals. You want to use this component inside an old AngularJS view. You must "downgrade" it.

// 1. Your modern Angular v22 Component
import { Component, input } from "@angular/core";

@Component({
  selector: "modern-user-profile",
  standalone: true,
  template: `
    <div class="profile-card">
      <h2>{{ userName() }}</h2>
      <p>Status: Active</p>
    </div>
  `,
})
export class ModernUserProfileComponent {
  // Using modern Signals for inputs
  userName = input.required<string>();
}
Enter fullscreen mode Exit fullscreen mode

Now, we bridge it to the legacy world:

// 2. The Bridge File (downgrade.ts)
import { downgradeComponent } from "@angular/upgrade/static";
import * as angular from "angular";
import { ModernUserProfileComponent } from "./modern-user-profile.component";

// Register the modern component as an AngularJS directive
angular.module("myLegacyApp").directive(
  "modernUserProfile",
  downgradeComponent({
    component: ModernUserProfileComponent,
  }) as angular.IDirectiveFactory,
);
Enter fullscreen mode Exit fullscreen mode

You can now use <modern-user-profile user-name="ctrl.name"></modern-user-profile> directly inside your old .html templates!

Upgrading an AngularJS Service

What if you have a massive, 2000-line AuthService in AngularJS that you aren't ready to rewrite, but you need to use it in your new Angular v22 components? You can "upgrade" it.

// 1. The legacy AngularJS Service
angular.module("myLegacyApp").service("LegacyAuthService", function () {
  this.getToken = function () {
    return localStorage.getItem("token");
  };
});
Enter fullscreen mode Exit fullscreen mode
// 2. The Bridge File (upgrade.ts)
import { FactoryProvider } from "@angular/core";

// Create a factory provider to pull the service from the AngularJS injector
export function legacyAuthServiceFactory(i: any) {
  return i.get("LegacyAuthService");
}

export const legacyAuthProvider: FactoryProvider = {
  provide: "LegacyAuthService",
  useFactory: legacyAuthServiceFactory,
  deps: ["$injector"], // This is the AngularJS $injector
};
Enter fullscreen mode Exit fullscreen mode

You can now provide this in your modern Angular application and inject it into your new components.


A Step-by-Step Migration Roadmap

Migrating is a marathon, not a sprint. Follow these five distinct phases to ensure success.

(Note: Managing this roadmap requires dedicated engineering bandwidth. If your team is too busy delivering product features to manage this, MigrateNg can execute this entire roadmap in parallel with your feature development.)

Phase 1: Preparation and Auditing

Before writing a single line of modern Angular code, you must clean house.

  • Delete Dead Code: Run coverage tools and audit your application. Delete any screens, components, or services that are no longer used. Do not migrate garbage.
  • Establish a TypeScript Baseline: If your AngularJS application is written in plain JavaScript, your first step is converting it to TypeScript. Rename .js files to .ts, configure a tsconfig.json with loose settings, and slowly add types. Modern Angular strictly requires TypeScript; doing this now flattens the learning curve.
  • Adopt Component-Based Architecture: If you are still using $scope and ng-controller across massive views, refactor them into AngularJS Components (.component()). Modern Angular is entirely component-driven.

Phase 2: Modularization

Legacy AngularJS applications are often massive monoliths registered to a single module (angular.module('app', [])).
Break your application into distinct, feature-based modules (e.g., UsersModule, BillingModule, InventoryModule). This decoupling allows you to migrate one specific slice of the application at a time without breaking the rest of the system.

Phase 3: The Hybrid Setup

Introduce the modern Angular v22 CLI and build system. Move your legacy code into the new folder structure. Implement the hybrid bootstrap process outlined in the code snippets above. At this stage, your application runs exactly as it did before, but the modern Angular v22 engine is humming quietly in the background, ready to take over.

Phase 4: Bottom-Up Porting (The "Leaf" Strategy)

Do not start by migrating your most complex dashboard. Start at the bottom of the dependency tree.
Identify "leaf" or "dumb" components—buttons, specialized inputs, formatting pipes, or simple display cards that do not depend on massive services.

  1. Rewrite the leaf component in modern Angular v22 using Standalone Components.
  2. Downgrade it using ngUpgrade.
  3. Replace the legacy usages with the new downgraded component.
  4. Delete the legacy AngularJS component.

As you migrate the leaves, you slowly work your way up the branches, eventually reaching the heavy feature containers and routing components.

Phase 5: Routing and State Management

This is the final and most complex phase. The goal is to entirely phase out legacy routing and state mechanisms.

State Management: As you migrate components, move away from $rootScope, broadcast events, and custom pub/sub event buses. Embrace Angular v22 Signals for synchronous state. Signals provide a guaranteed, glitch-free reactive graph that is significantly easier to reason about than $scope.$watch cascades. For asynchronous state, adopt the Resource API to natively handle loading and error states without writing boilerplate RxJS operators.

Hybrid Routing: Once an entire feature module is migrated, move its routing configuration from ui-router or ngRoute to the modern @angular/router. You will run a hybrid router setup for a time, where certain URL paths are handled by AngularJS and others by modern Angular. Angular provides the SetUpLocationSync module to ensure both routers stay synchronized with the browser's URL bar.

To set up hybrid routing, you must configure the modern router to handle specific paths and let the legacy router handle the rest (the "sink" route):

// app.routes.ts
import { Routes } from "@angular/router";
import { ModernDashboardComponent } from "./dashboard/modern-dashboard.component";

export const routes: Routes = [
  // 1. A fully migrated route handled by modern Angular v22
  { path: "dashboard", component: ModernDashboardComponent },

  // 2. The "sink" route: Anything not matching above falls back to AngularJS
  { path: "**", children: [] },
];
Enter fullscreen mode Exit fullscreen mode
// main.ts (Updated for routing)
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { UpgradeModule } from "@angular/upgrade/static";
import { setUpLocationSync } from "@angular/router/upgrade";
import { AppModule } from "./app/app.module";

platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .then((platformRef) => {
    const upgrade = platformRef.injector.get(UpgradeModule);
    upgrade.bootstrap(document.body, ["myLegacyApp"], { strictDi: true });

    // Keep both routers in sync!
    setUpLocationSync(upgrade);
  })
  .catch((err) => console.error(err));
Enter fullscreen mode Exit fullscreen mode

This ensures that as users navigate through your application, they seamlessly transition between legacy views and modernized views without realizing they are crossing a framework boundary.


Testing the Hybrid Application

Testing is often the casualty of a complex migration. When you introduce a hybrid architecture, your testing strategy must evolve.

1. Unit Testing the Boundary

Unit testing modern Angular v22 components is straightforward using the standard TestBed. However, testing a modern component that injects a legacy AngularJS service (via an upgrade provider) requires special care. You must ensure the legacy module is loaded in your Karma/Jasmine or Jest setup before the modern component is initialized. Conversely, testing legacy components that use downgraded modern services requires bootstrapping the ngUpgrade module in the test environment.

2. End-to-End (E2E) Testing is Your Safety Net

Because unit tests often mock out the complex boundaries between the two frameworks, E2E testing becomes your most critical safety net. Frameworks like Cypress or Playwright do not care if a button is rendered by AngularJS or Angular v22—they only care if it exists in the DOM and functions correctly.

Before beginning Phase 1 of your migration, write a comprehensive suite of E2E tests covering your most critical user flows. Run this suite continuously on every PR during the migration. If an upgraded component breaks a legacy feature, your E2E suite will catch the regression before it hits production.


Common Pitfalls and How to Avoid Them

Even with a perfect roadmap, hybrid migrations have traps.

1. Scope Bleed and Change Detection Nightmares

When passing data between AngularJS and modern Angular components across the ngUpgrade boundary, you are crossing two entirely different change detection mechanisms. If a modern Angular component updates a value, the AngularJS digest cycle ($apply) might not know about it, leaving the UI out of sync.
The Fix: Always be mindful of the boundary. Use manual ChangeDetectorRef calls in modern Angular, or $scope.$applyAsync() in legacy code when necessary. Better yet, migrate related components together to minimize the number of times data crosses the boundary.

2. The Bundle Size Penalty

During the hybrid phase, you are shipping both framework engines to your users. Your bundle size will be temporarily larger.
The Fix: Use lazy loading aggressively. If a route is entirely modernized, ensure the legacy AngularJS code is not loaded when navigating to it. Keep the hybrid phase as short as practically possible.

3. Underestimating the Skill Gap

Your engineering team knows AngularJS. They understand $scope, $q promises, and digest cycles.
Modern Angular v22 requires them to understand TypeScript decorators, Dependency Injection hierarchies, Signals, OnPush semantics, and Standalone architecture. Expecting your team to learn this on the fly while simultaneously untangling legacy spaghetti code is a recipe for burnout.

The Fix: Invest heavily in training. Include the obvious approaches like pair programming and migration code reviews but also incorporate group learning opportunities—lunch and learns, workshops, or basically anything with food and drink.


Conclusion: The Payoff

Migrating from AngularJS to Angular v22 is not just a maintenance chore—it is a massive upgrade to your organization's engineering velocity, application security, and runtime efficiency.

By avoiding the "Big Bang" rewrite and leveraging ngUpgrade for a strategic, bottom-up hybrid migration, you can deliver modern features while systematically erasing technical debt. The journey requires discipline, but a lightweight, zoneless, signal-driven application is well worth it.

Let Us Handle the Heavy Lifting

An enterprise migration requires dedicated architectural oversight and hundreds of hours of painstaking refactoring. If your engineering team is already stretched thin delivering business-critical features, you do not have to tackle this alone.

MigrateNg is a productized service by Westside Consulting designed explicitly for this challenge. We partner with your team to provide:

  • Comprehensive Codebase Audits: We map your dependency tree and identify the exact path of least resistance.
  • Parallel Execution: Our expert engineers execute the hybrid setup and bottom-up component porting while your team continues to build new features.
  • Team Upskilling: We don't just throw code over the wall. We train your engineers on modern Angular v22 best practices (Signals, Zoneless, Resource API) so they are ready to take the reins.

Stop risking your application's future on an EOL framework. Visit migrate.westsideconsulting.dev to schedule your architecture audit and begin your migration journey today.

Top comments (0)