angular-migration について
angular-migrationは、AngularJSアプリケーションをAngularへ移行するチーム向けのスキルです。ハイブリッド運用、書き換え、垂直スライスの戦略に加え、コンポーネント、サービス、依存性注入、ルーティング、検証を案内します。参照先の補助ファイルは含まれていません。
主な機能
- 01移行戦略とマイルストーンの計画
- 02AngularJSとAngularの並行運用
- 03コントローラーとディレクティブの変換
- 04サービスと依存性注入の移行
- 05ルーティングの更新と変更の検証
活用シーン
- 01AngularJSアプリケーションの刷新
- 02ハイブリッド運用を続けながら段階的に移行
- 03ディレクティブをAngularコンポーネントに置換
- 04ルーティングと依存性注入の変更計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill angular-migrationインストール後、Claude Code で/angular-migrationを入力して読み込まれたか確認します。
詳細
補足資料の入手可能性
このカタログのコピーには以下のインラインガイダンスが含まれていますが、次の補足パスは含まれていません。これらは外部の前提資料であり、同梱ファイルではありません。
<support-root>/references/hybrid-mode.md<support-root>/references/component-migration.md<support-root>/references/dependency-injection.md<support-root>/references/routing.md<support-root>/assets/hybrid-bootstrap.ts<support-root>/assets/migration-timeline.md<support-root>/scripts/analyze-angular-app.sh
これらのパスに依存する手順を実行する前に、元の補足資料または既存のインストール環境を求めてください。このカタログには、確認済みのダウンロード元が記載されていません。各パスをそのインストール環境を基準に解決し、内容を確認したうえで、該当するライセンスと実行要件を検証してください。必要なファイルが存在することを確認するまでは、コマンドを実行したり、参照された手順に従ったと主張したりしないでください。不足しているスクリプトを捏造したり、同じ名前の無関係なファイルを同等のものとして扱ったりしないでください。ここに完全に記載されている独立した手順は、ユーザーが許可した範囲内で実行できます。依存する手順は利用できないものとして報告してください。
Angular の移行
ハイブリッドアプリ、コンポーネント変換、依存性注入の変更、ルーティングの移行など、AngularJS から Angular への移行を習得します。
このスキルを使う場面
- AngularJS(1.x)アプリケーションを Angular(2+)に移行する場合
- AngularJS と Angular のハイブリッドアプリを実行する場合
- ディレクティブをコンポーネントに変換する場合
- 依存性注入を最新化する場合
- ルーティングシステムを移行する場合
- 最新の Angular バージョンに更新する場合
- Angular のベストプラクティスを導入する場合
このスキルを使わない場面
- AngularJS から Angular への移行ではない場合
- アプリがすでに最新の Angular バージョンを使用している場合
- フレームワークを変更せずに、小規模な UI 修正だけが必要な場合
手順
- AngularJS のコードベースと依存関係を評価し、移行リスクを確認します。
- 移行戦略(ハイブリッド方式または書き換え)を選び、マイルストーンを定めます。
- ngUpgrade をセットアップし、モジュール、コンポーネント、ルーティングを移行します。
- テストで検証し、安全な切り替えを計画します。
安全性
- ロールバックとステージング環境での検証を行わずに、一括で切り替えることは避けてください。
- 段階的な移行の間は、ハイブリッド互換性テストを継続してください。
移行戦略
1. 一括移行(全面的な書き換え)
- アプリ全体を Angular で書き換える
- 並行して開発する
- 一度に切り替える
- 最適な対象: 小規模なアプリ、新規プロジェクト
2. 段階的移行(ハイブリッド方式)
- AngularJS と Angular を並行して実行する
- 機能ごとに移行する
- 相互運用には ngUpgrade を使用する
- 最適な対象: 大規模なアプリ、継続的デリバリー
3. 垂直スライス
- 1つの機能を完全に移行する
- 新機能は Angular で実装し、既存機能は AngularJS で維持する
- 徐々に置き換える
- 最適な対象: 中規模のアプリ、機能が明確に分かれているアプリ
以下は部分的な移行例であり、ブートストラップ全体を示すものではありません。AngularJS のモジュール名(myAngularJSApp と myApp)を統一し、コンポーネントとフォームを登録または宣言的にインポートし、HttpClient を設定して、互換性のある Angular/TypeScript/Node のバージョンを選択してください。DI デコレーターの断片には、実際の NgModule クラスとインポートが必要です。AngularJS の $http はレスポンスのラッパーを解決しますが、HttpClient はレスポンス本文を解決します。この意味上の違いを維持してください。トラフィックを切り替える前に、ルーティングの所有権と変更検知をテストしてください。
ハイブリッドアプリのセットアップ
// 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
}
}
コンポーネントの移行
AngularJS コントローラー → Angular コンポーネント
// Before: AngularJS controller
angular.module('myApp').controller('UserController', function($scope, UserService) {
$scope.user = {};
$scope.loadUser = function(id) {
UserService.getUser(id).then(function(response) {
$scope.user = response.data;
});
};
$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).subscribe({
next: saved => { this.user = saved; },
error: error => { /* Display an appropriate UI error; do not log user data. */ }
});
}
}
AngularJS ディレクティブ → Angular コンポーネント
// 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>
サービスの移行
// 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);
}
}
依存性注入の変更
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());
});
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());
}
}
ルーティングの移行
// 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 {}
フォームの移行
<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);
}
}
移行のタイムライン
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
リソース
- references/hybrid-mode.md:ハイブリッドアプリのパターン
- references/component-migration.md:コンポーネント変換ガイド
- references/dependency-injection.md:DI の移行戦略
- references/routing.md:ルーティングの移行
- assets/hybrid-bootstrap.ts:ハイブリッドアプリのテンプレート
- assets/migration-timeline.md:プロジェクト計画
- scripts/analyze-angular-app.sh:アプリ分析スクリプト
ベストプラクティス
- サービスから始める:まずサービスを移行します(より簡単です)
- 段階的に進める:機能ごとに移行します
- 継続的にテストする:各ステップでテストします
- TypeScript を使用する:早い段階で TypeScript に移行します
- スタイルガイドに従う:初日から Angular のスタイルガイドに従います
- 最適化は後で行う:まず動作させてから、最適化します
- 文書化する:移行メモを残します
よくある落とし穴
- ハイブリッドアプリを正しく設定しない
- ロジックより先に UI を移行する
- 変更検知の違いを無視する
- スコープを適切に処理しない
- パターン(AngularJS と Angular)を混在させる
- テストが不十分
制限事項
- 上記の説明にある範囲にタスクが明確に一致する場合に限り、このスキルを使用してください。
- 出力を、環境固有の検証、テスト、または専門家によるレビューの代わりとして扱わないでください。
- 必要な入力、権限、安全上の境界、または成功基準が不足している場合は、作業を中止して確認を求めてください。