Angular 迁移

angular-migration
分类通用
作者Agentic Awesome Skills 社区
许可MIT
评分4.50/5
使用15.3K

Angular 迁移

掌握从 AngularJS 到 Angular 的迁移,包括混合应用、组件转换、依赖注入变更以及路由迁移。

适用场景

  • 将 AngularJS (1.x) 应用程序迁移到 Angular (2+)
  • 运行 AngularJS/Angular 混合应用程序
  • 将指令 (directives) 转换为组件 (components)
  • 现代化依赖注入 (DI)
  • 迁移路由系统
  • 更新至最新的 Angular 版本
  • 实施 Angular 最佳实践

不适用场景

  • 非 AngularJS 到 Angular 的迁移
  • 应用已处于现代 Angular 版本
  • 仅需简单的 UI 修复且无需框架变更

操作指南

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

安全注意事项

  • 避免在没有回滚机制和预发布环境验证的情况下进行“大爆炸”式切换。
  • 在增量迁移过程中保持混合兼容性测试。

迁移策略

1. 大爆炸 (完全重写)

  • 使用 Angular 重写整个应用
  • 并行开发
  • 一次性切换
  • 适用场景: 小型应用、全新项目

2. 增量迁移 (混合模式)

  • AngularJS 和 Angular 并行运行
  • 按功能模块逐一迁移
  • 使用 ngUpgrade 实现互操作
  • 适用场景: 大型应用、持续交付

3. 垂直切片 (Vertical Slice)

  • 完全迁移单个功能点
  • 新功能使用 Angular 开发,旧功能在 AngularJS 中维护
  • 逐步替换
  • 适用场景: 中型应用、功能界限清晰的应用

混合应用配置

typescript
// main.ts - 引导混合应用
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);
// 引导 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() {
// 在 main.ts 中手动引导
}
}

组件迁移

AngularJS Controller $\rightarrow$ Angular Component

javascript
// 迁移前: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
// 迁移后: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);
}
}

code
### AngularJS 指令 → Angular 组件
javascript
// 之前:AngularJS 指令
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>

};
});
code
typescript
// 之后:Angular 组件
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>();
}

// 使用方式: <app-user-card [user]="user" (delete)="handleDelete()"></app-user-card>

code
## 服务迁移 (Service Migration)
javascript
// 之前:AngularJS 服务
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);
}
};
});
code
typescript
// 之后:Angular 服务
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);
}
}

code
## 依赖注入变更

降级:Angular → AngularJS

typescript // Angular 服务 import { Injectable } from '@angular/core';

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

// 使其在 AngularJS 中可用
import { downgradeInjectable } from '@angular/upgrade/static';

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

// 在 AngularJS 中使用
angular.module('myApp').controller('OldController', function(newService) {
console.log(newService.getData());
});

code
### 升级:AngularJS → Angular
typescript
// AngularJS 服务
angular.module('myApp').factory('oldService', function() {
return {
getData: function() {
return 'data from AngularJS';
}
};
});

// 使其在 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']
}
]
})

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

code
## 路由迁移
javascript
// 之前:AngularJS 路由
angular.module('myApp').config(function($routeProvider) {
$routeProvider
.when('/users', {
template: '<user-list></user-list>'
code
})
.when('/users/:id', {
template: '<user-detail></user-detail>'
});
});

typescript
// 迁移后:Angular 路由
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
<!-- 迁移前: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
// 迁移后:Angular (模板驱动表单)
@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>
  
})

// 或响应式表单 (推荐)
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);
}
}

迁移时间线

code
第一阶段:准备 (1-2 周)
  • 安装 Angular CLI
  • 搭建混合应用 (Hybrid App)
  • 配置构建工具
  • 建立测试机制

第二阶段:基础设施 (2-4 周)

  • 迁移服务 (Services)

  • 迁移工具类 (Utilities)

  • 配置路由

  • 迁移共享组件

第三阶段:功能迁移 (时长不一)

  • 按功能模块逐一迁移

  • 进行彻底测试

  • 渐进式部署

第四阶段:清理 (1-2 周)

  • 移除 AngularJS 代码

  • 移除 ngUpgrade

  • 优化包体积

  • 最终测试

相关资源

  • 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. 遵循风格指南:从第一天起就遵循 Angular 风格指南
6. 后期优化:先确保功能可用,再进行优化
7. 记录文档:保留迁移笔记

常见陷阱

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

局限性

  • 仅在任务明确符合上述范围时使用此技能。
  • 不要将输出结果视为环境特定验证、测试或专家评审的替代方案。
  • 如果需要进一步澄清,请停止并询问。
缺少必要的输入、权限、安全边界或成功标准。