---
# === IDENTITY ===
id: software/migrations/angularjs-to-angular/2026
canonical_question: "How do I migrate from AngularJS 1.x to Angular 2+?"
aliases:
  - "AngularJS to Angular migration guide"
  - "upgrade AngularJS to Angular"
  - "AngularJS 1.x to Angular 19+ migration"
  - "ngUpgrade guide"
  - "AngularJS to Angular incremental migration"
  - "AngularJS EOL migration path"
entity_type: software_reference
domain: software > migrations > angularjs_to_angular
region: global
jurisdiction: global
temporal_scope: 2016-2026

# === VERIFICATION ===
last_verified: 2026-05-17
confidence: 0.93
version: 2.1
first_published: 2026-02-17

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: evolving
  last_breaking_change: "Angular 22 selectorless components + OnPush default (2026-05)"
  next_review: 2026-11-13
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "AngularJS must be >= 1.5.x (component API required for ngUpgrade compatibility)"
  - "AngularJS reached EOL December 2021 -- no security patches from Google; running in production is a security risk"
  - "Hybrid mode (both frameworks) roughly doubles bundle size and change detection cost -- minimize duration"
  - "Third-party AngularJS libraries (angular-ui-bootstrap, angular-material, angular-translate) have no ngUpgrade path -- must be replaced with Angular equivalents"
  - "Never manually call angular.bootstrap() alongside platformBrowserDynamic().bootstrapModule() -- use UpgradeModule for shared DI"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "User wants to migrate from AngularJS to React (not Angular)"
    use_instead: "software/migrations/angularjs-to-react/2026"
  - condition: "User is migrating between Angular versions (e.g., Angular 16 to 19), not from AngularJS"
    use_instead: "Use ng update via the Angular Update Guide at https://angular.dev/update-guide"
  - condition: "App is < 5,000 LOC and can tolerate a feature freeze"
    use_instead: "Full rewrite in Angular, React, or Vue -- hybrid setup overhead exceeds rewrite cost at this size"

# === AGENT HINTS ===
inputs_needed:
  - key: angularjs_version
    question: "What version of AngularJS is the app currently running?"
    type: choice
    options: ["1.2-1.4 (pre-component API)", "1.5-1.7", "1.8.x (final)"]
  - key: app_size
    question: "Roughly how large is the AngularJS codebase?"
    type: choice
    options: ["< 5,000 LOC", "5,000-50,000 LOC", "> 50,000 LOC"]
  - key: timeline
    question: "What is the migration timeline and can you do a feature freeze?"
    type: choice
    options: ["< 3 months (aggressive)", "3-6 months", "6-12 months (enterprise)", "No fixed deadline"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/migrations/angularjs-to-angular/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-05-17)"

# === RELATED UNITS ===
related_kos:
  depends_on:
    - id: "software/migrations/javascript-to-typescript/2026"
      label: "JavaScript to TypeScript Migration Guide"
  related_to:
    - id: "software/migrations/angular-to-react/2026"
      label: "Angular to React Migration Guide"
  alternative_to:
    - id: "software/migrations/angularjs-to-react/2026"
      label: "AngularJS to React Migration Guide"
  often_confused_with:
    - id: "software/migrations/angular-to-react/2026"
      label: "Angular (2+) to React Migration (different starting point — this card is AngularJS 1.x, not modern Angular)"

# === SOURCES (9 authoritative sources) ===
sources:
  - id: src1
    title: "Upgrading from AngularJS to Angular"
    author: Angular Team (Google)
    url: https://v17.angular.io/guide/upgrade
    type: official_docs
    published: 2023-11-08
    reliability: authoritative
  - id: src2
    title: "Migrating AngularJS to Angular with a Hybrid Application"
    author: Codurance
    url: https://www.codurance.com/publications/migrating-angularjs-to-angular
    type: technical_blog
    published: 2023-06-15
    reliability: high
  - id: src3
    title: "AngularJS to Angular Migration: The Ultimate SaaS Guide"
    author: The Frontend Company
    url: https://www.thefrontendcompany.com/posts/angularjs-to-angular-migration
    type: technical_blog
    published: 2024-08-20
    reliability: high
  - id: src4
    title: "AngularJS to Angular Upgrade: Common Migration Pitfalls"
    author: The Frontend Company
    url: https://www.thefrontendcompany.com/posts/angularjs-to-angular-upgrade-pitfalls
    type: technical_blog
    published: 2024-09-10
    reliability: high
  - id: src5
    title: "Migrate Your AngularJS Services to Angular with ngUpgrade"
    author: Sam Julien (DigitalOcean)
    url: https://www.digitalocean.com/community/tutorials/migrate-your-angularjs-services-to-angular-with-ngupgrade
    type: community_resource
    published: 2019-12-12
    reliability: high
  - id: src6
    title: "Planning for AngularJS EOL"
    author: OpenLogic by Perforce
    url: https://www.openlogic.com/blog/should-you-stay-angularjs
    type: industry_report
    published: 2024-03-15
    reliability: moderate_high
  - id: src7
    title: "UpgradeModule API Reference"
    author: Angular Team (Google)
    url: https://angular.dev/api/upgrade/static/UpgradeModule
    type: official_docs
    published: 2024-05-22
    reliability: authoritative
  - id: src8
    title: "AngularJS vs. Angular in 2026 — When and How to Migrate Your App"
    author: Monterail
    url: https://www.monterail.com/blog/angularjs-vs-angular-migration
    type: technical_blog
    published: 2025-01-15
    reliability: high
  - id: src9
    title: "Angular Version History: Every Release Date, Support Window, and End-of-Life Date from AngularJS to Angular 22"
    author: HeroDevs
    url: https://www.herodevs.com/blog-posts/angular-version-history-every-release-date-support-window-and-end-of-life-date-from-angularjs-to-angular-22
    type: industry_report
    published: 2026-05-13
    reliability: high
---

# How to Migrate from AngularJS 1.x to Angular

## TL;DR

- **Bottom line**: Use Angular's `@angular/upgrade` (ngUpgrade) module to run AngularJS and Angular side-by-side in a hybrid app, then migrate components incrementally bottom-up until no AngularJS code remains. [src1, src2]
- **Key tool/command**: `import { UpgradeModule } from '@angular/upgrade/static'`
- **Watch out for**: Running both frameworks doubles change detection cycles -- performance degrades significantly in large hybrid apps if you don't minimize the hybrid phase. [src3, src4]
- **Works with**: AngularJS 1.5+ (component API required) -> Angular 19-22. TypeScript 5.4+, Node.js 18+, Angular CLI 17+. Target Angular 20+ for standalone-by-default, stable signals, and zoneless change detection. Angular 19 reaches EOL 2026-05-19 -- prefer Angular 20-22 for new migrations. [src9]

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- AngularJS must be >= 1.5.x -- the `.component()` API introduced in 1.5 is required for ngUpgrade. Apps on 1.2-1.4 must first upgrade to 1.8.x. [src1]
- AngularJS reached EOL December 2021 -- no security patches from Google. Running AngularJS in production without HeroDevs NES (commercial LTS) is a security risk. [src6]
- Hybrid mode roughly doubles bundle size and change detection cost. Enterprise migrations should target 4-12 months maximum in hybrid state. [src3, src8]
- Third-party AngularJS libraries (angular-ui-bootstrap, angular-material, angular-translate) have no automatic ngUpgrade path -- budget time to replace each with Angular equivalents. [src3, src6]
- Never manually call `angular.bootstrap()` alongside `platformBrowserDynamic().bootstrapModule()` -- this creates two separate apps that cannot share DI. Always use `UpgradeModule`. [src1, src7]
- Angular 19+ defaults to `standalone: true` for all components. New components written during migration should use standalone APIs unless your hybrid setup requires NgModule. [src1]

## Quick Reference

| AngularJS Pattern | Angular Equivalent | Example |
|---|---|---|
| `angular.module('app', [])` | `@NgModule({ ... })` or standalone components | `@Component({ standalone: true, imports: [...] })` |
| `$scope` / `$rootScope` | Component class properties + `@Input`/`@Output` | `@Input() name: string;` |
| `.controller('Ctrl', fn)` | `@Component({ ... }) class Ctrl` | `@Component({ selector: 'app-ctrl', template: '...' })` |
| `.directive('myDir', fn)` | `@Directive({ selector: '[myDir]' })` or `@Component` | `@Directive({ selector: '[highlight]' })` |
| `.service('svc', fn)` / `.factory(...)` | `@Injectable({ providedIn: 'root' })` | `@Injectable({ providedIn: 'root' }) class SvcService` |
| `.filter('fmt', fn)` | `@Pipe({ name: 'fmt' })` | `@Pipe({ name: 'fmt' }) class FmtPipe implements PipeTransform` |
| `$http.get(url)` | `HttpClient.get(url)` | `this.http.get<Data[]>('/api/data')` |
| `$q.defer()` / promises | RxJS `Observable` (preferred) or native `Promise` | `this.http.get(url).pipe(map(res => res.data))` |
| `$routeProvider` / `ui-router` | `RouterModule.forRoot(routes)` | `{ path: 'home', component: HomeComponent }` |
| `ng-repeat="item in items"` | `*ngFor="let item of items"` (or `@for` in Angular 17+) | `@for (item of items; track item.id) { ... }` |
| `ng-if="condition"` | `*ngIf="condition"` (or `@if` in Angular 17+) | `@if (isLoaded) { <app-data /> }` |
| `ng-model="value"` | `[(ngModel)]="value"` (FormsModule) or reactive forms | `<input [(ngModel)]="name">` / `formControlName="name"` |
| `$broadcast` / `$emit` | `EventEmitter`, RxJS `Subject`, or Angular Signals | `@Output() changed = new EventEmitter<string>();` |
| `$watch('expr', fn)` | RxJS streams, `OnChanges`, or Angular Signals + `effect()` | `effect(() => console.log(this.count()))` |
| `$compile(html)($scope)` | Dynamic components via `ViewContainerRef` | `viewRef.createComponent(MyComponent)` |

## Decision Tree

```
START
|-- Is your AngularJS app < 5,000 LOC?
|   |-- YES -> Consider a full rewrite (faster than hybrid setup overhead) [see When to Use]
|   +-- NO v
|-- Is the app on AngularJS 1.5+ with .component() API?
|   |-- YES -> Proceed with ngUpgrade incremental migration [see Step 1]
|   +-- NO -> First upgrade to AngularJS 1.8.x and refactor to component architecture [see Step 1]
|-- Do you use ui-router extensively?
|   |-- YES -> Install @uirouter/angular-hybrid for parallel routing [see Code Examples]
|   +-- NO v
|-- Do you need both frameworks' routes simultaneously?
|   |-- YES -> Use setUpLocationSync() from @angular/upgrade/static [see Step 5]
|   +-- NO v
|-- Is the team experienced with TypeScript?
|   |-- YES -> Migrate services first (bottom-up), then components
|   +-- NO -> Invest 2-4 weeks in TypeScript training before starting migration
|-- Target Angular version?
|   |-- Angular 19+ -> Use standalone components by default, plan for zoneless (Angular 21+)
|   +-- Angular 17-18 -> Use NgModule or standalone (both supported)
+-- DEFAULT -> Use ngUpgrade hybrid approach, migrate leaf components first, work upward
```

## Step-by-Step Guide

### 1. Prepare the AngularJS codebase

Refactor the AngularJS app to follow the component-based architecture introduced in AngularJS 1.5. Convert controllers + templates into `.component()` definitions. Follow the "Rule of One" -- one component per file. If on AngularJS 1.2-1.4, first upgrade to 1.8.x. [src1, src2]

```javascript
// BEFORE: controller-based (AngularJS 1.x style)
angular.module('app').controller('HeroListCtrl', function($scope, HeroService) {
  $scope.heroes = [];
  HeroService.getAll().then(function(heroes) {
    $scope.heroes = heroes;
  });
});

// AFTER: component-based (AngularJS 1.5+ style -- required for ngUpgrade)
angular.module('app').component('heroList', {
  template: '<div ng-repeat="hero in $ctrl.heroes">{{hero.name}}</div>',
  controller: function(HeroService) {
    this.$onInit = function() {
      HeroService.getAll().then(heroes => this.heroes = heroes);
    };
  }
});
```

**Verify**: Count remaining `$scope` references: `grep -r "\$scope" src/ --include="*.js" | wc -l` -> target: `0`

### 2. Set up the Angular project alongside AngularJS

Create a new Angular project using Angular CLI. Install the upgrade package. Configure the build system to compile both AngularJS and Angular code. [src1, src7]

```bash
# Create Angular project (if not already existing)
ng new my-app --skip-install
cd my-app

# Install ngUpgrade
npm install @angular/upgrade

# If using ui-router, install the hybrid adapter
npm install @uirouter/angular-hybrid
```

```typescript
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { UpgradeModule } from '@angular/upgrade/static';

@NgModule({
  imports: [
    BrowserModule,
    UpgradeModule
  ]
})
export class AppModule {
  constructor(private upgrade: UpgradeModule) {}

  ngDoBootstrap() {
    // Bootstrap AngularJS inside Angular
    this.upgrade.bootstrap(document.body, ['myAngularJSApp'], { strictDi: true });
  }
}
```

**Verify**: `ng build` compiles without errors. The app loads and AngularJS components still render.

### 3. Downgrade an Angular component for use in AngularJS

Create your first Angular component. Use `downgradeComponent` to register it as an AngularJS directive so it can be used in AngularJS templates. [src1, src2]

```typescript
// src/app/hero-detail/hero-detail.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-hero-detail',
  template: `<h3>{{ hero.name }}</h3><p>ID: {{ hero.id }}</p>`
})
export class HeroDetailComponent {
  @Input() hero: { id: number; name: string };
}
```

```typescript
// src/app/hero-detail/hero-detail.downgrade.ts
import { downgradeComponent } from '@angular/upgrade/static';
import { HeroDetailComponent } from './hero-detail.component';

// Register as AngularJS directive
angular.module('myAngularJSApp')
  .directive('appHeroDetail', downgradeComponent({
    component: HeroDetailComponent
  }));
```

```html
<!-- Now usable in any AngularJS template -->
<app-hero-detail [hero]="$ctrl.selectedHero"></app-hero-detail>
```

**Verify**: The Angular component renders inside AngularJS templates. Inspect the DOM -- the element should have an `ng-version` attribute.

### 4. Migrate services from AngularJS to Angular

Rewrite AngularJS services as Angular `@Injectable` classes. Downgrade them for backward compatibility with remaining AngularJS components. [src2, src5]

```typescript
// src/app/services/hero.service.ts -- new Angular service
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

interface Hero {
  id: number;
  name: string;
}

@Injectable({ providedIn: 'root' })
export class HeroService {
  constructor(private http: HttpClient) {}

  getAll(): Observable<Hero[]> {
    return this.http.get<Hero[]>('/api/heroes');
  }

  getById(id: number): Observable<Hero> {
    return this.http.get<Hero>(`/api/heroes/${id}`);
  }
}
```

```typescript
// Downgrade for AngularJS consumption
import { downgradeInjectable } from '@angular/upgrade/static';
import { HeroService } from './services/hero.service';

angular.module('myAngularJSApp')
  .factory('heroService', downgradeInjectable(HeroService));
```

```javascript
// AngularJS component can now use the Angular service
angular.module('myAngularJSApp').component('heroList', {
  template: '...',
  controller: function(heroService) {
    // heroService is the downgraded Angular service
    // Returns Observable -- convert to promise for AngularJS
    heroService.getAll().toPromise().then(heroes => {
      this.heroes = heroes;
    });
  }
});
```

**Verify**: AngularJS components using the downgraded service still function. Check network tab -- HTTP calls go through Angular's HttpClient.

### 5. Set up routing coexistence

Configure Angular Router alongside AngularJS routing. Use `setUpLocationSync()` to keep both routers synchronized. [src1, src2]

```typescript
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  // New Angular routes
  { path: 'dashboard', component: DashboardComponent },
  { path: 'heroes/:id', component: HeroDetailComponent },
  // Catch-all: let AngularJS handle unknown routes
  { path: '**', component: AngularJSRouteComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}
```

```typescript
// In AppModule -- sync location between frameworks
import { setUpLocationSync } from '@angular/upgrade/static';
import { Router } from '@angular/router';

export class AppModule {
  constructor(private upgrade: UpgradeModule, private router: Router) {}

  ngDoBootstrap() {
    this.upgrade.bootstrap(document.body, ['myAngularJSApp'], { strictDi: true });
    setUpLocationSync(this.upgrade);
  }
}
```

**Verify**: Navigate between AngularJS and Angular routes. URL changes reflect in both frameworks. Browser back/forward buttons work correctly.

### 6. Migrate components bottom-up

Work from leaf components (no child dependencies) upward. For each component: rewrite in Angular, replace the AngularJS directive with a downgraded version, remove the old AngularJS code. Track progress by counting remaining AngularJS controllers. [src2, src3]

```bash
# Track migration progress
echo "AngularJS controllers remaining:"
grep -r "\.controller(" src/ --include="*.js" | wc -l

echo "AngularJS components remaining:"
grep -r "\.component(" src/ --include="*.js" | wc -l

echo "Angular components created:"
find src/app -name "*.component.ts" | wc -l
```

**Verify**: After each component migration, run the full test suite: `ng test && npm run e2e`

### 7. Remove AngularJS and finalize

Once all components and services are migrated, remove ngUpgrade, AngularJS dependencies, and the hybrid bootstrap. Switch to pure Angular bootstrap. For Angular 19+, migrate to standalone bootstrap using `bootstrapApplication`. [src1, src3]

```bash
# Remove AngularJS dependencies
npm uninstall angular @angular/upgrade
# If using ui-router hybrid
npm uninstall @uirouter/angular-hybrid @uirouter/angularjs

# Remove from angular.json -- delete any AngularJS script includes
# Update main.ts to standard Angular bootstrap
```

```typescript
// src/main.ts -- clean Angular bootstrap (standalone, Angular 19+)
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';

bootstrapApplication(AppComponent, appConfig)
  .catch(err => console.error(err));
```

**Verify**: `ng build --configuration production` succeeds. `npm run test` passes. No references to `angular.module`, `$scope`, or `@angular/upgrade` in codebase.

## Code Examples

### TypeScript/Angular: Upgrading an AngularJS Service for Use in Angular

```typescript
// Input:  An existing AngularJS service registered as angular.module('app').service('userService', ...)
// Output: An Angular provider that wraps the AngularJS service for use in Angular components

// Step 1: Create a provider factory that pulls from the AngularJS injector
// src/app/ajs-upgraded-providers.ts
import { InjectionToken } from '@angular/core';

// Define a token for the AngularJS service
export const USER_SERVICE = new InjectionToken<any>('UserService');

// Factory that extracts the AngularJS service via $injector
export function userServiceFactory(i: any) {
  return i.get('userService');
}

// Provider configuration for Angular module
export const userServiceProvider = {
  provide: USER_SERVICE,
  useFactory: userServiceFactory,
  deps: ['$injector']
};
```

```typescript
// Step 2: Use the upgraded service in an Angular component
// src/app/profile/profile.component.ts
import { Component, Inject, OnInit } from '@angular/core';
import { USER_SERVICE } from '../ajs-upgraded-providers';

@Component({
  selector: 'app-profile',
  template: `
    <div *ngIf="user">
      <h2>{{ user.name }}</h2>
      <p>{{ user.email }}</p>
    </div>
  `
})
export class ProfileComponent implements OnInit {
  user: any;

  constructor(@Inject(USER_SERVICE) private userService: any) {}

  ngOnInit() {
    // The AngularJS service returns a promise
    this.userService.getCurrentUser().then((user: any) => {
      this.user = user;
    });
  }
}
```

### TypeScript/Angular: Downgrading an Angular Component with Inputs/Outputs

```typescript
// Input:  An Angular component with @Input and @Output bindings
// Output: A downgraded AngularJS directive that preserves all bindings

// src/app/search-bar/search-bar.component.ts
import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: 'app-search-bar',
  template: `
    <input [value]="query" (input)="onInput($event)" placeholder="Search...">
    <button (click)="onSearch()">Search</button>
  `
})
export class SearchBarComponent {
  @Input() query: string = '';
  @Output() queryChange = new EventEmitter<string>();
  @Output() search = new EventEmitter<string>();

  onInput(event: Event) {
    this.query = (event.target as HTMLInputElement).value;
    this.queryChange.emit(this.query);
  }

  onSearch() {
    this.search.emit(this.query);
  }
}
```

```typescript
// src/app/search-bar/search-bar.downgrade.ts
import { downgradeComponent } from '@angular/upgrade/static';
import { SearchBarComponent } from './search-bar.component';

angular.module('myAngularJSApp')
  .directive('appSearchBar', downgradeComponent({
    component: SearchBarComponent
  }));
```

```html
<!-- Usage in AngularJS template -- note: kebab-case attributes for Angular bindings -->
<app-search-bar
  [query]="$ctrl.searchQuery"
  (query-change)="$ctrl.searchQuery = $event"
  (search)="$ctrl.doSearch($event)">
</app-search-bar>
```

### JavaScript: Automated Migration Progress Tracker Script

> Full script: [javascript-automated-migration-progress-tracker-sc.js](scripts/javascript-automated-migration-progress-tracker-sc.js) (56 lines)

```javascript
// Input:  Project source directory path
// Output: Migration progress report (AngularJS vs Angular component counts)
// scripts/migration-progress.js
const { execSync } = require('child_process');

function countPattern(dir, pattern, ext) {
  try {
    const result = execSync(
      `grep -r "${pattern}" "${dir}" --include="*.${ext}" -l 2>/dev/null | wc -l`,
      { encoding: 'utf-8' }
    );
    return parseInt(result.trim(), 10);
  } catch { return 0; }
}
# ... (see full script)
```

## Anti-Patterns

### Wrong: Bootstrapping AngularJS manually alongside Angular

```typescript
// BAD -- manually calling angular.bootstrap creates two separate apps
// that cannot share services or components
platformBrowserDynamic().bootstrapModule(AppModule);
angular.bootstrap(document.body, ['myApp']); // Separate bootstrap!
```

### Correct: Use UpgradeModule to bootstrap AngularJS within Angular

```typescript
// GOOD -- UpgradeModule creates a single hybrid app with shared DI
@NgModule({
  imports: [BrowserModule, UpgradeModule]
})
export class AppModule {
  constructor(private upgrade: UpgradeModule) {}
  ngDoBootstrap() {
    this.upgrade.bootstrap(document.body, ['myApp'], { strictDi: true });
  }
}

platformBrowserDynamic().bootstrapModule(AppModule);
```

### Wrong: Using $scope in migrated components

```javascript
// BAD -- $scope creates tight coupling to AngularJS digest cycle
// and cannot be migrated to Angular
angular.module('app').component('userCard', {
  controller: function($scope, UserService) {
    $scope.user = null;
    $scope.$watch('userId', function(id) {
      UserService.get(id).then(function(u) { $scope.user = u; });
    });
  }
});
```

### Correct: Use component bindings and lifecycle hooks

```javascript
// GOOD -- component bindings map directly to Angular @Input/@Output
angular.module('app').component('userCard', {
  bindings: {
    userId: '<'
  },
  controller: function(UserService) {
    this.$onChanges = function(changes) {
      if (changes.userId && changes.userId.currentValue) {
        UserService.get(this.userId).then(u => this.user = u);
      }
    };
  }
});
```

### Wrong: Migrating everything at once (big bang)

```bash
# BAD -- rewriting the entire app in one branch
git checkout -b full-angular-rewrite
# ... 6 months of parallel development ...
# Result: massive merge conflicts, feature drift, missed deadlines
```

### Correct: Incremental migration with continuous deployment

```bash
# GOOD -- migrate one component at a time, merge to main frequently
git checkout -b migrate/hero-detail-component
# Migrate one component, downgrade for AngularJS compatibility
# Run tests, deploy, merge
git checkout -b migrate/hero-service
# Next component...
```

### Wrong: Duplicating state between frameworks

```typescript
// BAD -- maintaining separate state in both Angular and AngularJS
// leads to synchronization bugs
// In Angular:
this.cartItems = [...]; // Angular state
// In AngularJS:
$scope.cartItems = [...]; // AngularJS state -- gets out of sync!
```

### Correct: Single source of truth via downgraded/upgraded services

```typescript
// GOOD -- one service owns the state, shared via downgrade
@Injectable({ providedIn: 'root' })
export class CartService {
  private items = new BehaviorSubject<CartItem[]>([]);
  items$ = this.items.asObservable();

  addItem(item: CartItem) {
    this.items.next([...this.items.value, item]);
  }
}

// Downgrade for AngularJS
angular.module('app').factory('cartService', downgradeInjectable(CartService));
```

### Wrong: Ignoring Zone.js performance in hybrid mode

```typescript
// BAD -- every AngularJS digest triggers Angular change detection
// and vice versa, creating cascading cycles
// No optimization = 2x-5x slower than either framework alone
```

### Correct: Use OnPush change detection in migrated components

```typescript
// GOOD -- OnPush prevents unnecessary change detection in hybrid mode
@Component({
  selector: 'app-hero-list',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `<div *ngFor="let hero of heroes$ | async">{{ hero.name }}</div>`
})
export class HeroListComponent {
  heroes$ = this.heroService.getAll();
  constructor(private heroService: HeroService) {}
}
```

### Wrong: Using deprecated .toPromise() for Observable-to-Promise conversion

```typescript
// BAD -- .toPromise() is deprecated since RxJS 7
const heroes = await this.heroService.getAll().toPromise();
```

### Correct: Use firstValueFrom() or lastValueFrom()

```typescript
// GOOD -- firstValueFrom() is the modern replacement (RxJS 7+)
import { firstValueFrom } from 'rxjs';

const heroes = await firstValueFrom(this.heroService.getAll());
```

## Common Pitfalls

- **Dual change detection performance hit**: Running both AngularJS digest cycle and Angular Zone.js change detection simultaneously can degrade performance 2-5x. Fix: Use `ChangeDetectionStrategy.OnPush` on all migrated Angular components and minimize cross-framework component boundaries. [src2, src4]
- **$scope.$apply errors in hybrid mode**: Calling `$scope.$apply()` when Angular Zone.js already triggers a digest causes "$apply already in progress" errors. Fix: Remove explicit `$scope.$apply()` calls; Zone.js handles triggering the AngularJS digest automatically in hybrid mode. [src1, src4]
- **Routing conflicts between frameworks**: Both AngularJS and Angular routers try to handle URL changes, causing infinite loops or blank pages. Fix: Call `setUpLocationSync(this.upgrade)` after bootstrap, and ensure routes are partitioned -- each URL is handled by exactly one framework. [src1, src2]
- **Missing entryComponents declaration**: Downgraded Angular components must be listed in both `declarations` and `entryComponents` arrays in NgModule (for Angular < 13; Ivy makes this optional). Fix: Add the component to `entryComponents: [MyComponent]` in the NgModule, or upgrade to Angular 13+ where Ivy handles this automatically. [src1, src7]
- **Observable vs Promise confusion**: AngularJS uses `$q` promises everywhere; Angular uses RxJS Observables. Mixing them without conversion causes silent failures. Fix: Use `firstValueFrom()` (RxJS 7+) when passing Observable results to AngularJS code. `.toPromise()` is deprecated. [src5]
- **Dependency injection token mismatch**: AngularJS uses string tokens (`'myService'`); Angular uses class references or `InjectionToken`. Downgraded/upgraded services must use the correct token type for each framework. Fix: Use `downgradeInjectable(ServiceClass)` with a matching `.factory('serviceName', ...)` registration. [src5, src7]
- **Third-party AngularJS library incompatibility**: Libraries like `angular-ui-bootstrap`, `angular-translate`, or `angular-material` have no ngUpgrade path. Fix: Replace with Angular equivalents (Angular Material, ngx-translate, ng-bootstrap) before or during migration, or wrap with a thin AngularJS component boundary. [src3, src6]
- **Forgetting to plan for zoneless and OnPush defaults**: Angular 21+ defaults to zoneless change detection; Angular 22 (May 2026) makes OnPush the default change-detection strategy for new components. Migration code written against pre-22 defaults must be re-validated. Fix: Prefer `signal()`, `computed()`, and `effect()` in new components; assume OnPush semantics from day one. [src4, src9]

## Decision Logic

### If app is AngularJS 1.2-1.4
--> First upgrade to AngularJS 1.8.x and refactor controllers into `.component()` definitions. The `.component()` API (added in 1.5) is required for ngUpgrade — there is no path to skip this step. [src1, src2]

### If app is < 5,000 LOC and tolerates a feature freeze
--> Do a full rewrite in Angular 20-22 (standalone components, signals). Hybrid setup overhead exceeds rewrite cost at this size. [src3, src8]

### If app is 5,000-50,000 LOC with active feature development
--> Use ngUpgrade incremental migration with `UpgradeModule`. Migrate leaf components first (bottom-up), then services, then routing. Budget 4-9 months in hybrid mode. [src1, src2, src8]

### If app is > 50,000 LOC (enterprise)
--> Use ngUpgrade with route-based partitioning. Migrate one feature module at a time, deploy continuously. Budget 9-18 months. Hire or train Angular/TypeScript expertise before starting. [src2, src3, src8]

### If routing uses ui-router
--> Install `@uirouter/angular-hybrid` and use it as the bridge instead of `setUpLocationSync()`. ui-router state-based routing maps cleanly to Angular Router via the hybrid adapter. [src1, src2]

### If target is Angular 22 (May 2026 release)
--> Adopt OnPush as the default change-detection strategy in all new components (Angular 22 default) and prefer Signal Forms over reactive forms for new screens. Skip `NgModule` — go standalone-only. [src1, src7, src9]

### If target was Angular 19
--> Plan an immediate follow-on upgrade to Angular 20 or 21. Angular 19 reaches EOL on 2026-05-19 — running a freshly migrated app on an EOL framework defeats the purpose of the migration. [src9]

### If AngularJS app cannot be taken off production during migration
--> Stay on commercial AngularJS LTS (HeroDevs NES) for security patches while migrating, then cut over once hybrid mode is gone. Do not run unsupported AngularJS in production without commercial support. [src6, src9]

## Diagnostic Commands

```bash
# Check AngularJS version
node -e "console.log(require('./node_modules/angular/package.json').version)"

# Check Angular CLI and framework version
ng version

# Count remaining AngularJS artifacts
grep -r "angular\.module\|\.controller(\|\.directive(\|\.factory(\|\.service(" src/ --include="*.js" | wc -l

# Find all $scope references (should be 0 before migration)
grep -rn "\$scope" src/ --include="*.js" --include="*.ts"

# Verify hybrid bootstrap is working (check browser console)
# In browser DevTools console:
# angular.element(document.body).injector()  -- should return AngularJS injector
# ng.getComponent(document.querySelector('app-root'))  -- should return Angular root

# Check for circular dependencies
npx madge --circular src/app

# Verify no AngularJS code remains after migration
grep -r "angular\." src/ --include="*.ts" --include="*.js" | grep -v node_modules | grep -v ".spec."

# Run Angular production build to catch any issues
ng build --configuration production

# Analyze bundle size (check for leftover AngularJS)
npx webpack-bundle-analyzer dist/my-app/stats.json

# Check for deprecated APIs before removing Zone.js (Angular 19+)
grep -rn "NgZone\|onMicrotaskEmpty\|onUnstable\|isStable\|onStable" src/ --include="*.ts"
```

## Version History & Compatibility

| Version | Status | ngUpgrade Support | Migration Notes |
|---|---|---|---|
| AngularJS 1.8.x | EOL (Dec 2021) | Required source | Last AngularJS release. Must be on 1.5+ for component API. [src6] |
| Angular 2-5 | EOL | Full | Original ngUpgrade. Requires SystemJS or custom webpack config. |
| Angular 6-8 | EOL | Full | Angular CLI builder. `@angular/upgrade/static` is the stable API. |
| Angular 9-12 | EOL | Full (Ivy) | Ivy renderer. `entryComponents` still required until Angular 13. |
| Angular 13-15 | EOL | Full (Ivy) | `entryComponents` removed. `ViewEngine` dropped in 13. |
| Angular 16-17 | EOL / LTS | Full | Standalone components, signals, new control flow syntax (`@if`, `@for`). [src1] |
| Angular 18 | EOL | Full | Experimental zoneless change detection. Signal-based components. |
| Angular 19 | LTS (EOL 2026-05-19) | Full | `standalone: true` by default. Stable signals API. Upgrade to 20+ before EOL. [src7, src9] |
| Angular 20 | LTS (until Nov 2026) | Full | Zoneless stable. Signal-based components mature. |
| Angular 21 | Active support | Full | Zoneless by default. Angular MCP Server + AI-assisted dev tooling. [src9] |
| Angular 22 | Active (released May 2026) | Full | Selectorless components, stable Signal Forms, OnPush default, TypeScript 5.9. [src9] |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Existing AngularJS app > 5,000 LOC with active development | App is < 5,000 LOC or in maintenance-only mode | Full rewrite in Angular, React, or Vue |
| Team has Angular/TypeScript skills or budget to train | Team has no TypeScript experience and no training budget | Consider React (gentler JS-first learning curve) |
| Business requires continuous feature delivery during migration | App can go into feature freeze for 3-6 months | Big-bang rewrite during freeze period |
| Shared services and state make incremental approach necessary | Frontend and backend are being rewritten simultaneously | Greenfield Angular project from scratch |
| AngularJS security patches are needed (EOL means no patches) | Using HeroDevs NES (Never-Ending Support) for AngularJS | Stay on AngularJS with commercial LTS support |
| You want to stay in the Angular ecosystem with familiar patterns | Team prefers a completely different paradigm | Migrate to React, Vue, or Svelte instead |

## Important Caveats

- AngularJS reached End of Life in December 2021. No security patches, bug fixes, or browser compatibility updates are provided by Google. Running AngularJS in production is a security risk. HeroDevs offers commercial "Never-Ending Support" as an alternative. [src6]
- The hybrid phase (running both frameworks) should be as short as possible. Every week in hybrid mode incurs double the bundle size, double the change detection cost, and increased complexity. Enterprise migrations typically take 4-12 months. [src3, src8]
- Angular's Ivy compiler (v9+) changed how downgraded components are compiled. If migrating from a pre-Ivy hybrid setup, re-test all downgraded components after enabling Ivy. [src1]
- Zone.js is deprecated in Angular 21+ (zoneless by default) and Angular 22 (May 2026) makes OnPush the default change-detection strategy. New Angular components written during migration should use signals and OnPush change detection for future compatibility. Plan ahead -- migrating to zoneless after removing AngularJS avoids doing two migrations. [src4, src9]
- Third-party AngularJS libraries (angular-ui-bootstrap, angular-material, etc.) must be replaced with Angular equivalents -- there is no automatic migration path for these. Budget extra time for replacing UI component libraries. [src3]
- TypeScript strict mode (`strict: true` in tsconfig.json) is strongly recommended but may require significant type annotation effort when migrating untyped AngularJS JavaScript code. [src2]
- Angular 19 defaults `standalone: true` for components. If your hybrid setup relies on NgModule, you may need to explicitly set `standalone: false` on some components during the migration, then switch to standalone after removing AngularJS. [src1]

## Related Units

- [JavaScript to TypeScript Migration Guide](/software/migrations/javascript-to-typescript/2026) (depends_on — TypeScript skills required)
- [AngularJS to React Migration Guide](/software/migrations/angularjs-to-react/2026) (alternative_to — different target framework)
- [Angular to React Migration Guide](/software/migrations/angular-to-react/2026) (often_confused_with — different starting point: modern Angular, not AngularJS)
