mcpField
A
angular-migration

angular-migration

github.com/sickn33开发49.1k275 次安装今天更新
代码仓库

关于 angular-migration

angular-migration 是帮助团队将 AngularJS 应用迁移到 Angular 的技能,介绍混合运行、重写和垂直切片策略,并指导组件、服务、依赖注入、路由和验证;所引用的辅助文件未随附。

主要功能

  1. 01规划迁移策略和里程碑
  2. 02让 AngularJS 与 Angular 并行运行
  3. 03转换控制器和指令
  4. 04迁移服务与依赖注入
  5. 05更新路由并验证变更

适用场景

  1. 01现代化 AngularJS 应用
  2. 02逐步迁移并维持混合应用
  3. 03将指令替换为 Angular 组件
  4. 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 修复,无需更改框架

说明

  1. 评估 AngularJS 代码库、依赖项和迁移风险。
  2. 选择迁移策略(混合迁移或重写),并确定里程碑。
  3. 设置 ngUpgrade,迁移模块、组件和路由。
  4. 通过测试进行验证,并规划安全切换。

安全

  • 避免在没有回滚方案和预发布环境验证的情况下进行一次性全面切换。
  • 在增量迁移期间持续进行混合兼容性测试。

迁移策略

1. 一次性重写(完全重写)
  • 使用 Angular 重写整个应用
  • 并行开发
  • 一次性切换
  • 适用场景: 小型应用、全新项目
2. 增量迁移(混合方式)
  • 并行运行 AngularJS 和 Angular
  • 按功能逐步迁移
  • 使用 ngUpgrade 实现互操作
  • 适用场景: 大型应用、持续交付
3. 垂直切片
  • 完整迁移一个功能
  • 使用 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:应用分析脚本

最佳实践

  1. 从服务开始:先迁移服务(更简单)
  2. 循序渐进:按功能逐步迁移
  3. 持续测试:每一步都进行测试
  4. 使用 TypeScript:尽早迁移到 TypeScript
  5. 遵循风格指南:从第 1 天起遵循 Angular 风格指南
  6. 稍后优化:先让它正常运行,再进行优化
  7. 记录文档:保留迁移笔记

常见陷阱

  • 未正确配置混合应用
  • 先迁移 UI,再迁移逻辑
  • 忽略变更检测差异
  • 未正确处理作用域
  • 混用模式(AngularJS + Angular)
  • 测试不足

局限性

  • 仅当任务明确符合上述范围时,才使用此技能。
  • 不要将输出视为环境特定验证、测试或专家审查的替代品。
  • 如果缺少必要的输入、权限、安全边界或成功标准,请暂停并请求澄清。

评论

支持 Markdown