Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Migrate from AngularJS to Angular using hybrid mode, incremental component rewriting, and dependency injection updates. Use when upgrading AngularJS applications, planning framework migrations, or modernizing legacy Angular code.
.claude/skills/dicklesworthstone-angular-migration/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 79% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 96% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 225% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 128% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 161% | 0% |
Master AngularJS to Angular migration, including hybrid apps, component conversion, dependency injection changes, and routing migration.
typescript// 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 }); });
typescript// 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 } }
javascript// 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); }; });
typescript// 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); } }
javascript// 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> `, }; });
typescript// 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>
javascript// 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); }, }; });
typescript// 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); } }
typescript// 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()); });
typescript// 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()); } }
javascript// 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>", }); });
typescript// 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 {}
html<!-- 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>
typescript// 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); } }
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| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 15,015 | 7,376 | -51% | 1 | 1 | 0% | 2,276 | 3,811 | +67% | 0 | 0 | — |
case-02 | pass→pass | 14,753 | 11,456 | -22% | 1 | 1 | 0% | 2,199 | 4,365 | +98% | 0 | 0 | — |
case-03 | fail→pass | 18,022 | 14,076 | -22% | 1 | 1 | 0% | 2,540 | 4,553 | +79% | 0 | 0 | — |
case-04 | fail→pass | 16,451 | 17,088 | +4% | 1 | 1 | 0% | 2,799 | 5,483 | +96% | 0 | 0 | — |
case-05 | pass→pass | 6,837 | 5,388 | -21% | 1 | 1 | 0% | 1,283 | 3,514 | +174% | 0 | 0 | — |
case-06 | pass→pass | 15,789 | 13,496 | -15% | 1 | 1 | 0% | 2,505 | 5,110 | +104% | 0 | 0 | — |
case-07 | pass→pass | 13,968 | 10,744 | -23% | 1 | 1 | 0% | 2,501 | 4,356 | +74% | 0 | 0 | — |
case-08 | pass→pass | 9,304 | 7,419 | -20% | 1 | 1 | 0% | 1,779 | 3,920 | +120% | 0 | 0 | — |
case-09 | fail→pass | 21,435 | 5,245 | -76% | 1 | 1 | 0% | 1,066 | 3,465 | +225% | 0 | 0 | — |
case-10 | pass→pass | 11,748 | 17,485 | +49% | 1 | 1 | 0% | 2,042 | 5,329 | +161% | 0 | 0 | — |
case-11 | pass→pass | 9,990 | 5,434 | -46% | 1 | 1 | 0% | 1,760 | 3,620 | +106% | 0 | 0 | — |
case-12 | fail→pass | 8,835 | 7,092 | -20% | 1 | 1 | 0% | 1,570 | 3,583 | +128% | 0 | 0 | — |
case-13 | fail→pass | 9,463 | 11,578 | +22% | 1 | 1 | 0% | 1,688 | 4,409 | +161% | 0 | 0 | — |
case-14 | pass→pass | 13,194 | 13,050 | -1% | 1 | 1 | 0% | 2,528 | 4,503 | +78% | 0 | 0 | — |
case-15 | pass→pass | 8,568 | 9,635 | +12% | 1 | 1 | 0% | 1,450 | 4,330 | +199% | 0 | 0 | — |
case-16 | pass→pass | 18,282 | 20,208 | +11% | 1 | 1 | 0% | 2,737 | 5,544 | +103% | 0 | 0 | — |
case-17 | pass→pass | 15,678 | 14,313 | -9% | 1 | 1 | 0% | 2,424 | 4,845 | +100% | 0 | 0 | — |
case-18 | pass→pass | 18,955 | 9,463 | -50% | 1 | 1 | 0% | 1,684 | 4,053 | +141% | 0 | 0 | — |
case-19 | pass→pass | 14,212 | 9,713 | -32% | 1 | 1 | 0% | 2,001 | 4,235 | +112% | 0 | 0 | — |
case-20 | pass→pass | 3,120 | 3,030 | -3% | 1 | 1 | 0% | 502 | 3,045 | +507% | 0 | 0 | — |
case-21 | pass→pass | 4,275 | 5,627 | +32% | 1 | 1 | 0% | 624 | 3,536 | +467% | 0 | 0 | — |
case-22 | pass→pass | 9,628 | 30,784 | +220% | 1 | 1 | 0% | 1,858 | 4,540 | +144% | 0 | 0 | — |
case-23 | pass→pass | 14,842 | 15,427 | +4% | 1 | 1 | 0% | 2,540 | 4,994 | +97% | 0 | 0 | — |
case-24 | pass→pass | 16,575 | 17,202 | +4% | 1 | 1 | 0% | 3,031 | 5,610 | +85% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +21 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.