angular-migration
Install this skill
npx skills add wshobson/agentsWorks 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
- Evaluate the codebase size to choose between big-bang or incremental migration strategy
- Configure the hybrid app setup by installing and importing the UpgradeModule
- Bootstrap the dual-framework environment to ensure both versions share the DOM
- Systematically refactor individual services to use HttpClient and RxJS
- Convert low-level UI components or directives to Angular components one feature at a time
- 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
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.
π Full skill instructions β original source: wshobson/agents
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)
- Click "Download" above
- In your project, create the directory:
.agent/skills/angular-migration/ - Save the file as
SKILL.md - 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

