如何将AngularJS 1.6升级到Angular 5?ngUpgrade实现遇阻求助
我之前帮过几个类似的AngularJS到Angular的升级项目,结合你的情况(AngularJS 1.6 + ui-router + MVC打包),给你梳理一套可行的ngUpgrade落地步骤,一步步来就能实现混合运行,再逐步迁移组件:
第一步:搭建Angular 5环境并引入ngUpgrade
首先你需要在现有项目中添加Angular 5的依赖,建议用npm安装核心包:
npm install @angular/core@5 @angular/common@5 @angular/platform-browser@5 @angular/upgrade@5 core-js zone.js systemjs --save
然后在你的MVC项目中,把这些依赖的脚本(比如core-js.min.js、zone.js.min.js、system.js)放到Scripts目录,同时创建Angular的基础结构:
- 创建
app文件夹,里面放app.module.ts和main.ts - 配置
system.config.js来解析Angular模块(SystemJS是Angular 5默认的模块加载器,适合MVC这种非Webpack打包的场景)
编写Angular的根模块,引入UpgradeModule来桥接AngularJS:
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() { // 通过UpgradeModule启动原有的AngularJS应用 this.upgrade.bootstrap(document.body, ['myApp'], { strictDi: true }); } }
再写main.ts启动Angular:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule);
第二步:修改入口页面,兼容混合模式
你的index.cshtml里原来的ng-app指令要去掉,因为现在由Angular的UpgradeModule来启动AngularJS应用:
<body> <div ui-view></div> <!-- 引入Angular依赖和启动脚本 --> <script src="~/Scripts/core-js.min.js"></script> <script src="~/Scripts/zone.js.min.js"></script> <script src="~/Scripts/system.js"></script> <script src="~/Scripts/system.config.js"></script> <script> System.import('~/app/main.js').catch(err => console.error(err)); </script> </body>
第三步:适配ui-router,实现混合路由
因为你现有项目用的是AngularJS版的ui-router,建议先保留它来管理所有路由,逐步把单个视图替换成Angular组件。比如针对你的heroController对应的路由:
1. 新建Angular组件
先写一个Angular版的Hero组件:
import { Component } from '@angular/core'; @Component({ selector: 'app-hero', template: ` <!-- 这里写Angular组件的模板,替代原来的AngularJS视图 --> <h1>Hero Component (Angular 5)</h1> ` }) export class HeroComponent {}
2. 把Angular组件降级为AngularJS指令
在AppModule里注册这个组件,并用downgradeComponent把它转换成AngularJS能识别的指令:
import { downgradeComponent } from '@angular/upgrade/static'; import { HeroComponent } from './hero.component'; @NgModule({ declarations: [HeroComponent], imports: [BrowserModule, UpgradeModule] }) export class AppModule { constructor(private upgrade: UpgradeModule) {} ngDoBootstrap() { // 注入AngularJS的$compileProvider,注册降级后的指令 this.upgrade.injector.get('$injector').invoke(['$compileProvider', ($compileProvider: angular.ICompileProvider) => { $compileProvider.directive('appHero', downgradeComponent({ component: HeroComponent }) as angular.IDirectiveFactory); }]); this.upgrade.bootstrap(document.body, ['myApp'], { strictDi: true }); } }
3. 修改ui-router状态
把原来指向AngularJS控制器的模板,换成这个降级后的指令:
myApp.config(function($stateProvider) { $stateProvider.state('hero', { url: '/hero', // 替换原来的ng-controller模板 template: '<app-hero></app-hero>' }); });
现在访问/hero路由,就能看到Angular 5的组件了,原有的AngularJS应用其他部分不受影响。
第四步:逐步迁移控制器/服务
对于你项目中大量的控制器、指令和服务,可以分两种方式迁移:
方式1:升级AngularJS服务到Angular
如果要复用原有的服务逻辑,可以把AngularJS的服务升级为Angular可注入的服务。比如把你的heroController先改成AngularJS工厂(因为控制器不适合直接升级):
myApp.factory('heroService', ['$http', function($http) { // 把原来heroController里的业务逻辑迁移到这里 return { getHeroes: function() { return $http.get('/api/heroes'); } }; }]);
然后在Angular里用upgradeInjectable升级这个服务:
import { Injectable } from '@angular/core'; import { upgradeInjectable } from '@angular/upgrade/static'; @Injectable() export class HeroService extends upgradeInjectable('heroService') {}
在AppModule的providers数组里添加这个服务,Angular组件就能直接注入使用了。
方式2:重写为Angular组件/服务
对于复杂的控制器,建议直接重写为Angular组件,逐步替换掉原有的AngularJS代码。
关键注意事项
- 开启严格DI:启动AngularJS时添加
strictDi: true,能帮你快速找到未标注依赖的服务,避免迁移时出现隐式依赖问题。 - 数据交互:如果需要Angular组件和AngularJS代码共享数据,可以用
@Input()/@Output()传递,或者通过升级后的共享服务来同步。 - MVC打包:如果用ASP.NET MVC的BundleConfig,要把Angular编译后的
main.js、vendor.js等加入到bundle中,确保加载顺序正确。 - 小步迭代:每次只迁移一个组件或服务,测试通过后再继续,避免一次性修改太多导致排查困难。
内容的提问来源于stack exchange,提问作者Ankur

