Back to TypeScript

angular-migration

AngularAngularJSMigrationFrontendTypeScriptHybrid AppngUpgradeCode Modernization
⭐ 36.8kπŸ“„ MITπŸ•’ 2026-06-16Source β†—

Install this skill

npx skills add wshobson/agents

Works across Claude Code, Cursor, Codex, Copilot & Antigravity

The angular-migration skill provides a technical framework for upgrading legacy AngularJS (1.x) applications to modern Angular (2+). It focuses on architectural transition strategies including hybrid coexistence, where both frameworks operate within the same browser runtime using ngUpgrade. This skill automates the translation of outdated scope-based controllers into TypeScript-based components and converts legacy directives into modular component structures. It handles the shift from promise-based services to reactive RxJS observables and manages dependency injection patterns that differ significantly between versions. By applying these structural patterns, developers can systematically decouple legacy codebases, ensuring that routing, services, and view logic remain functional throughout the transition period. This skill reduces the technical debt associated with manual refactoring by enforcing standardized conversion patterns for common AngularJS structures.

When to Use This Skill

  • β€’Gradually replacing AngularJS modules with Angular components in a large enterprise system
  • β€’Standardizing service layers when moving from $http to Angular's HttpClient
  • β€’Converting legacy template-heavy directives into modern, testable Angular components
  • β€’Setting up a side-by-side hybrid environment to prevent project downtime during transitions

How to Invoke This Skill

Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:

  • β€œMigrate this AngularJS controller to an Angular component
  • β€œHow do I set up a hybrid AngularJS and Angular app
  • β€œRefactor this legacy directive to an Angular directive
  • β€œConvert this factory service to an injectable service
  • β€œExplain the process for upgrading AngularJS to the latest Angular
  • β€œHelp me bridge dependency injection between AngularJS and Angular

Pro Tips

  • πŸ’‘Prioritize migrating core services and shared utilities first to establish a stable foundation before tackling UI components.
  • πŸ’‘Implement thorough end-to-end tests for both AngularJS and Angular parts of your hybrid app to catch regressions early.
  • πŸ’‘Leverage the Angular CLI for creating new components and modules, ensuring adherence to best practices and a consistent project structure.

What this skill does

  • β€’Hybrid application bootstrapping for simultaneous Angular and AngularJS runtime execution
  • β€’Conversion of scope-reliant controllers into class-based components
  • β€’Mapping of legacy directives to modern Angular component decorators
  • β€’Migration of factory-based services to @Injectable services with HttpClient
  • β€’Implementation of ngUpgrade for cross-framework dependency injection
  • β€’Refactoring of promise-based data fetching into RxJS observable streams

When not to use it

  • βœ•When the legacy application is small enough to be rewritten entirely in a single sprint
  • βœ•When moving to a completely different frontend framework like React or Vue
  • βœ•When the legacy codebase lacks sufficient test coverage to verify refactoring integrity

Example workflow

  1. Evaluate the codebase size to choose between big-bang or incremental migration strategy
  2. Configure the hybrid app setup by installing and importing the UpgradeModule
  3. Bootstrap the dual-framework environment to ensure both versions share the DOM
  4. Systematically refactor individual services to use HttpClient and RxJS
  5. Convert low-level UI components or directives to Angular components one feature at a time
  6. Remove AngularJS dependencies as components reach full feature parity

Prerequisites

  • –Existing AngularJS 1.x codebase
  • –Basic knowledge of TypeScript
  • –Familiarity with Angular CLI and project structure

Pitfalls & limitations

  • !Maintaining dual-framework state synchronization can lead to complex bugs
  • !Over-reliance on $scope variables during the transition phase
  • !Dependency injection conflicts when providers are defined in both frameworks
  • !Performance overhead of running two frameworks simultaneously

FAQ

What is the primary benefit of a hybrid approach?
It allows you to ship new features in Angular while keeping legacy functionality active, avoiding a complete production freeze.
Do I need to rewrite my CSS during this migration?
Not necessarily; however, wrapping legacy CSS into Angular component styles often improves maintainability.
Can I use both frameworks indefinitely?
While possible, it is meant as a transition state. Maintaining a hybrid environment long-term increases technical debt and bundle sizes.
How are services shared between AngularJS and Angular?
The UpgradeModule allows you to downgrade Angular services so they can be injected into AngularJS, or upgrade AngularJS services for Angular.

How it compares

This skill automates the syntactic and structural boilerplate of common patterns, preventing the typical errors associated with manual manual refactoring of dependency injection or scope binding.

Source & trust

⭐ 37k starsπŸ“„ MITπŸ•’ Updated 2026-06-16
πŸ“„ Full skill instructions β€” original source: wshobson/agents
# Angular Migration

Master AngularJS to Angular migration, including hybrid apps, component conversion, dependency injection changes, and routing migration.

## When to Use This Skill

- Migrating AngularJS (1.x) applications to Angular (2+)
- Running hybrid AngularJS/Angular applications
- Converting directives to components
- Modernizing dependency injection
- Migrating routing systems
- Updating to latest Angular versions
- Implementing Angular best practices

## Migration Strategies

### 1. Big Bang (Complete Rewrite)

- Rewrite entire app in Angular
- Parallel development
- Switch over at once
- **Best for:** Small apps, green field projects

### 2. Incremental (Hybrid Approach)

- Run AngularJS and Angular side-by-side
- Migrate feature by feature
- ngUpgrade for interop
- **Best for:** Large apps, continuous delivery

### 3. Vertical Slice

- Migrate one feature completely
- New features in Angular, maintain old in AngularJS
- Gradually replace
- **Best for:** Medium apps, distinct features

## Hybrid App Setup

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

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


// 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() {
// Bootstrapped manually in main.ts
}
}


## Component Migration

### AngularJS Controller β†’ Angular Component

// Before: AngularJS controller
angular
.module("myApp")
.controller("UserController", function ($scope, UserService) {
$scope.user = {};

$scope.loadUser = function (id) {
UserService.getUser(id).then(function (user) {
$scope.user = user;
});
};

$scope.saveUser = function () {
UserService.saveUser($scope.user);
};
});


// After: Angular component
import { Component, OnInit } from "@angular/core";
import { UserService } from "./user.service";

@Component({
selector: "app-user",
template:
<div>
<h2>{{ user.name }}</h2>
<button (click)="saveUser()">Save</button>
</div>
,
})
export class UserComponent implements OnInit {
user: any = {};

constructor(private userService: UserService) {}

ngOnInit() {
this.loadUser(1);
}

loadUser(id: number) {
this.userService.getUser(id).subscribe((user) => {
this.user = user;
});
}

saveUser() {
this.userService.saveUser(this.user);
}
}


### AngularJS Directive β†’ Angular Component

// Before: AngularJS directive
angular.module("myApp").directive("userCard", function () {
return {
restrict: "E",
scope: {
user: "=",
onDelete: "&",
},
template:
<div class="card">
<h3>{{ user.name }}</h3>
<button ng-click="onDelete()">Delete</button>
</div>
,
};
});


// After: Angular component
import { Component, Input, Output, EventEmitter } from "@angular/core";

@Component({
selector: "app-user-card",
template:
<div class="card">
<h3>{{ user.name }}</h3>
<button (click)="delete.emit()">Delete</button>
</div>
,
})
export class UserCardComponent {
@Input() user: any;
@Output() delete = new EventEmitter<void>();
}

// Usage: <app-user-card [user]="user" (delete)="handleDelete()"></app-user-card>


## Service Migration

// Before: AngularJS service
angular.module("myApp").factory("UserService", function ($http) {
return {
getUser: function (id) {
return $http.get("/api/users/" + id);
},
saveUser: function (user) {
return $http.post("/api/users", user);
},
};
});


// After: Angular service
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs";

@Injectable({
providedIn: "root",
})
export class UserService {
constructor(private http: HttpClient) {}

getUser(id: number): Observable<any> {
return this.http.get(/api/users/${id});
}

saveUser(user: any): Observable<any> {
return this.http.post("/api/users", user);
}
}


## Dependency Injection Changes

### Downgrading Angular β†’ AngularJS

// Angular service
import { Injectable } from "@angular/core";

@Injectable({ providedIn: "root" })
export class NewService {
getData() {
return "data from Angular";
}
}

// Make available to AngularJS
import { downgradeInjectable } from "@angular/upgrade/static";

angular.module("myApp").factory("newService", downgradeInjectable(NewService));

// Use in AngularJS
angular.module("myApp").controller("OldController", function (newService) {
console.log(newService.getData());
});


### Upgrading AngularJS β†’ Angular

// AngularJS service
angular.module('myApp').factory('oldService', function() {
return {
getData: function() {
return 'data from AngularJS';
}
};
});

// Make available to Angular
import { InjectionToken } from '@angular/core';

export const OLD_SERVICE = new InjectionToken<any>('oldService');

@NgModule({
providers: [
{
provide: OLD_SERVICE,
useFactory: (i: any) => i.get('oldService'),
deps: ['$injector']
}
]
})

// Use in Angular
@Component({...})
export class NewComponent {
constructor(@Inject(OLD_SERVICE) private oldService: any) {
console.log(this.oldService.getData());
}
}


## Routing Migration

// Before: AngularJS routing
angular.module("myApp").config(function ($routeProvider) {
$routeProvider
.when("/users", {
template: "<user-list></user-list>",
})
.when("/users/:id", {
template: "<user-detail></user-detail>",
});
});


// After: Angular routing
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";

const routes: Routes = [
{ path: "users", component: UserListComponent },
{ path: "users/:id", component: UserDetailComponent },
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}


## Forms Migration

<!-- Before: AngularJS -->
<form name="userForm" ng-submit="saveUser()">
<input type="text" ng-model="user.name" required />
<input type="email" ng-model="user.email" required />
<button ng-disabled="userForm.$invalid">Save</button>
</form>


// After: Angular (Template-driven)
@Component({
template:
<form #userForm="ngForm" (ngSubmit)="saveUser()">
<input type="text" [(ngModel)]="user.name" name="name" required>
<input type="email" [(ngModel)]="user.email" name="email" required>
<button [disabled]="userForm.invalid">Save</button>
</form>

})

// Or Reactive Forms (preferred)
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
template:
<form [formGroup]="userForm" (ngSubmit)="saveUser()">
<input formControlName="name">
<input formControlName="email">
<button [disabled]="userForm.invalid">Save</button>
</form>

})
export class UserFormComponent {
userForm: FormGroup;

constructor(private fb: FormBuilder) {
this.userForm = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]]
});
}

saveUser() {
console.log(this.userForm.value);
}
}


## Migration Timeline

Phase 1: Setup (1-2 weeks)
- Install Angular CLI
- Set up hybrid app
- Configure build tools
- Set up testing

Phase 2: Infrastructure (2-4 weeks)
- Migrate services
- Migrate utilities
- Set up routing
- Migrate shared components

Phase 3: Feature Migration (varies)
- Migrate feature by feature
- Test thoroughly
- Deploy incrementally

Phase 4: Cleanup (1-2 weeks)
- Remove AngularJS code
- Remove ngUpgrade
- Optimize bundle
- Final testing


## Resources

- **references/hybrid-mode.md**: Hybrid app patterns
- **references/component-migration.md**: Component conversion guide
- **references/dependency-injection.md**: DI migration strategies
- **references/routing.md**: Routing migration
- **assets/hybrid-bootstrap.ts**: Hybrid app template
- **assets/migration-timeline.md**: Project planning
- **scripts/analyze-angular-app.sh**: App analysis script

## Best Practices

1. **Start with Services**: Migrate services first (easier)
2. **Incremental Approach**: Feature-by-feature migration
3. **Test Continuously**: Test at every step
4. **Use TypeScript**: Migrate to TypeScript early
5. **Follow Style Guide**: Angular style guide from day 1
6. **Optimize Later**: Get it working, then optimize
7. **Document**: Keep migration notes

## Common Pitfalls

- Not setting up hybrid app correctly
- Migrating UI before logic
- Ignoring change detection differences
- Not handling scope properly
- Mixing patterns (AngularJS + Angular)
- Inadequate testing

How to Use This Skill Unit

Option A: Project-Specific (Recommended)

  1. Click "Download" above
  2. In your project, create the directory: .agent/skills/angular-migration/
  3. Save the file as SKILL.md
  4. The agent will automatically discover the skill based on its description.

Option B: Global Installation (All Agents)

Save the file to these locations to make it available across all projects:

  • Claude Code: ~/.claude/skills/wshobson/agents/angular-migration/SKILL.md
  • Cursor: ~/.cursor/skills/wshobson/agents/angular-migration/SKILL.md
  • Antigravity: ~/.gemini/antigravity/skills/wshobson/agents/angular-migration/SKILL.md

πŸš€ Install with CLI:
npx skills add wshobson/agents

Read the Master Guide: Mastering Agent Skills β†’

Recommended Rules

View more rules β†’

Recommended Workflows

View more workflows β†’

Recommended MCP Servers

View more MCP servers β†’

Take It Further

Maximize your productivity with these powerful resources

πŸ“‹

Define Your Standards

Set up coding standards to ensure this workflow produces consistent, high-quality results.

Browse Rules Library
πŸ“–

Master Workflows

Learn how to create custom workflows, use Turbo Mode, and build your automation library.

Complete Guide

How to use this Skill in Claude Code & Cursor

For Claude Code (CLI)

To use this skill in Claude Code, copy the rule content into your project's custom instructions or follow our Add-Skill CLI guide. This ensures Claude follows your standards during every code generation.

For Cursor & Windsurf

For Cursor or Windsurf, individual skills are best used in the "Rules for AI" section. This specific unit helps the agent avoid typescript issues, leading to cleaner, more efficient code.

Why the skill format matters: the standardized Agent Skills format lets your AI agent load detailed instructions only when they are relevant, keeping your prompt clean while improving results.

Source & attribution

This skill is categorized under TypeScript and is published by W. Shobson, maintained in wshobson/agents.

← Browse All Agent Skills
Sponsored AI assistant. Recommendations may be paid.