ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Angular老项目升级全流程与避坑指南:从AngularJS到新版迁移实践

Angular老项目升级全流程与避坑指南:从AngularJS到新版迁移实践 老项目升级这事儿干过的人都知道表面上是换个版本号实际上是把一整套技术选型、依赖关系、写法习惯全盘翻新一遍。Angular 更是其中的硬骨头从 AngularJS 1.x 到 Angular 2 是推倒重来之后每个大版本又都带着一堆 breaking changes稍不留神就是“升级一时爽排错火葬场”。最近我们团队刚把两个老项目从 Angular 1.4.6 和 Angular 8 分别升了上来连着踩了大半个月的坑这期“Angular持续提升”系列第四篇就把从旧到新的升级全流程和核心注意事项整理出来给准备动手或者正在挣扎的朋友一份可以直接照着做的作业。这篇内容适合三种人一是手上还压着 AngularJS 老项目的维护者二是在 Angular 8/12 这类旧版本上想升级又不敢动的开发同学三是刚接手一个历史项目、被版本兼容问题折磨得头疼的新手。全篇以实操为主每个关键选择我都会解释背后的原因。版本升级这件事放到哪个技术领域都一样做网络的朋友会知道交换机固件一升配置、特性、指令集都可能变放到前端框架上残酷程度只高不低。1. 版本升级前的全局认知与准备工作1.1 为什么Angular版本升级这么痛苦Angular 的版本演进和其他框架不太一样它属于“激进型”框架。Angular 2 发布时直接抛弃了 AngularJS也就是 Angular 1.x的全部设计控制器、作用域、指令链、双向绑定那套哲学全部作废等于再造了一个新框架。后续从 Angular 4 到 Angular 18虽然大版本号一直在变但好在核心 API 基本延续只是每个版本都砍掉一批废弃 API、换一套工具链。所以升级的本质难点有两个一是老代码本身的迁移量二是依赖生态的连带升级。我见过不少团队把升级一拖再拖最后积压的版本跨度大到只能重写。这里有个很现实的规律升级成本随版本跨度指数增长而不是线性增长。你从 Angular 8 升到 9 可能只需半天但从 Angular 4 直接冲到 16可能一个月都不够。原因是编译器的变化、依赖冲突、API 废弃是层层叠加的一次性面对所有差异排查问题根本无从下手。1.2 升级前必须盘点的信息清单动手之前先把家底摸清这一步做足能省掉后面 80% 的麻烦。我会把 package.json 打开逐项过一遍同时跑ng version看一下当前 CLI、Core、编译器的具体版本。以下是我每次升级前固定做的清单当前 Angular 版本、Angular CLI 版本、TypeScript 版本、Node 版本、RxJS 版本第三方 UI 库和工具库清单比如 ng-zorro-antd、PrimeNG、Angular Material这些库往往绑定 Angular 大版本业务代码里有没有直接使用Renderer、ViewChild、动态组件工厂等底层 API项目里是否还混着老 AngularJS 代码有没有angular.js的全局依赖测试用例数量和基础配置升级后要第一时间跑回归。这一步的核心思路是确定“升级影响面”。Angular 的升级不只是升级框架本身CLI、TypeScript、Node、RxJS 全部是绑定关系。比如 Angular 18 要求 Node 20 左右、TypeScript 5.4 左右你本机如果还在用 Node 12那就得先把运行环境往前推一大步。建议用 nvm 管理 Node 版本按项目需求切换千万别为了一个老项目把自己的开发机环境搞成一团乱麻。1.3 先看官方升级指南再动手很多人不知道 Angular 官方有一个专门做升级规划的站点 update.angular.io我每次升级前都会去那里输入当前版本和目标版本它会直接生成一份针对性的升级清单列出现阶段的版本号、依赖调整建议、需要手工关注的 breaking changes。这个动作很便宜但能帮你避免很多“我以为没事结果跑起来全是错”的局面。同时要理解ng update的工作原理它是 Angular CLI 基于 schematics 机制实现升级能力读取 package.json 里的依赖版本比对当前版本与目标版本然后依次执行每个包内置的 migration schematics。这些 schematics 不仅会改依赖版本号还会自动改写部分代码和配置文件。所以严格来说ng update是一个半自动的代码迁移工具而不是简单的包管理器更新用的时候要把它当“改代码”来看待每一步都要仔细 review 改动。2. 核心升级路径与实操步骤2.1 从AngularJS 1.x跨到Angular的迁移路线先说最头疼的场景如果项目还停留在 AngularJS 1.x特别是像我们之前遇到的 1.4.6那不是一个“升级”能解决的因为 Angular 2 根本不兼容 AngularJS 的运行模型。这是一次架构级迁移通常有两条路第一条路是全部重写。适用条件是项目规模不大、业务逻辑不复杂、团队对新框架驾驭能力足够。说实话如果一个项目还压在 1.4.6 这种 2015 年的版本上且没有积累太多不可替代的东西重写反而更划算。AngularJS 1.4.6 连 1.8 时代的组件化 API 都不完整硬迁的代价极高。第二条路是渐进式迁移。把 AngularJS 代码通过官方UpgradeModule跑在一个混合应用里新功能用 Angular 写老功能继续跑在 AngularJS 里再用downgradeComponent和upgradeComponent在两个框架之间搭桥。这条路适合大系统但它要求先把 AngularJS 代码规范成组件写法Controller$scope 的老写法很难桥接所以实操上往往是先升 AngularJS 到 1.8再逐步把 Controller 改造成 Component API最后才引入 Angular 混合壳。我们在 1.4.6 老项目上实际采用的策略是先把 AngularJS 从 1.4.6 升到 1.8.x因为这个过程相对平滑主要是解决依赖和少量 API 兼容问题然后挑选业务价值最高、改动面最小的模块作为试点用 Angular 重写并由混合模式托管等新代码占比超过一半再彻底移除 AngularJS。整个过程大概持续了两三个迭代。这里必须提醒AngularJS 1.8 是官方最后一个版本安全补丁支持已经结束所以混合迁移阶段要控制时间别拖太久。2.2 小步快跑逐大版本升级实操对于已经在 Angular 2 上的项目我的经验是“一次只跨一个大版本”。虽然技术上可以跳过中间版本但官方 migration schematics 设计时是按版本衔接的跳版本会漏掉中间的自动迁移步骤导致大量手工补偿工作。实操命令很简单以从 Angular 8 升到 9 为例ng update angular/cli9 angular/core9这条命令会同时升级 CLI 框架包并触发相关的 migration schematics。注意我一次只指定一个目标大版本升级完成、测试通过之后再跑下一条ng update angular/cli10 angular/core10逐个版本推进虽然慢但每一步的变更范围可控出了问题可以快速定位到“这个版本引入的变化”。强烈建议升级前在 git 上拉一个独立分支并确保工作区干净。ng update默认会检查 git 状态有未提交修改时会拒绝执行。如果代码有冲突或迁移结果不理想直接切回老分支成本很低。升级后第一时间检查git diff重点看 schematics 自动改了哪些文件——这些改动往往比版本号变化本身更值得关注。2.3 第三方依赖与工具链协同升级Angular 升级最容易翻车的其实是第三方库。像 ng-zorro-antd 这类组件库每个大版本都绑定特定 Angular 版本UI 库不升级Angular 升上去了也会因为 peer dependency 冲突卡死。我的做法是升级 Angular 之前先把第三方库逐个大版本升到位或者至少确认目标库支持目标 Angular 版本。ng update支持同时更新多个包但如果包之间没有好的协同建议先单独升级第三方库再升 Angular 核心避免一次处理太多变量。这里必须提一个反面教材ng update --all --force。--all会把所有相关依赖一次性全部升级--force会忽略 peer dependency 的版本检查。我见过有人用这条命令一把梭结果 CLI 升到了 18、UI 库还停在 10项目跑起来全是样式错乱和类型错误最后花了整整一周回滚梳理。不是说这条命令不能用而是它应该用在“你已经确认了升级路线”的前提下。稳妥的做法是一次只升一个作用域每升一步都跑一遍构建和测试。工具链协同方面整理一个简化对照表供参考以官方文档为准Angular 大版本建议 Node 版本TypeScript 版本主要变化9 12~3.7Ivy 编译器默认启用11 12~4.0构建与测试改进13 12.20 4.4移除 View Engine不再支持 IE1114 14.15 4.6Standalone 组件预览15 14.20~4.8Standalone 稳定16^16.14 或 ^18.10 4.9Signal 引入17^18.13 或 ^20.9 5.2新控制流语法 if/for18^18.19 或 ^20.11 或 ^22~5.4zoneless、linkedSignal这张表每次升级前都要核对一遍Node 版本不对装依赖和编译会出各种莫名其妙的怪问题TypeScript 版本不对类型定义直接崩一片。3. 关键API变更与兼容性处理3.1 高频破坏性变更速查升级过程中真正耗时间的不是执行命令而是改掉被废弃的 API 和写法。我梳理了几个升级路上最常撞墙的破坏性变更写成速查表老写法新写法影响范围AngularJS$scope/controllerAngular Component架构级重写AngularJSfilter管道Pipe语法完全不同$http.get()HttpClient.get()包名、调用方式都变ng-repeat*ngFor/for模板语法变更RxJSObservable.of()/.pipe()链式旧方法of()pipe()管道操作符RxJS 5 升 6 的必修课View Engine 编译Ivy 编译Angular 9 起默认13 起移除 View EngineTestBed.get()TestBed.inject()测试代码全局替换依赖注入里的构造函数字符串写法仅类型写法Angular 14 后不支持字符串 token 的宽松解析很多同学升级后看到一个ExpressionChangedAfterItHasBeenCheckedError就慌其实这个错误在 Angular 4 就有了只是每次升级换模块时容易暴露。它本质上是你试图在变更检测跑完后再次修改某个绑定值属于代码设计问题不是升级本身的 bug排查思路是找到那个在生命周期里滞后改变值的属性把改动挪到更早的钩子里去。3.2 迁移实战AngularJS 1.4.6时代代码改造示例拿一段 1.4.6 时代的典型代码来说明迁移过程你感受下差异AngularJS 老写法angular.module(app, []) .controller(UserController, function($scope, $http) { $scope.user {}; $scope.loadUser function() { $http.get(/api/user).then(function(res) { $scope.user res.data; }); }; $scope.loadUser(); });对应的模板div ng-controllerUserController p{{ user.name }}/p ul li ng-repeatitem in user.list{{ item }}/li /ul /div迁移到 Angular 后的写法以 Angular 17 的新控制流为例Component({ selector: app-user, imports: [HttpClientModule], template: p{{ user().name }}/p ul for (item of user().list; track item) { li{{ item }}/li } /ul }) export class UserComponent { user signalUser({} as User); constructor(private http: HttpClient) { http.getUser(/api/user).subscribe(data this.user.set(data)); } }这里不只是语法变化整个思维模式都变了老代码靠作用域链传递状态新代码靠组件输入输出和响应式状态。迁移过程中最容易犯的错是把$scope的一对多绑定关系直接复制到 Angular 组件里然后把事件绑定写成乱七八糟的闭包通信。如果遇到跨组件状态同步优先考虑Input/Output或者用 service signal 管理共享状态别再走“全局对象随便赋值”的老路。3.3 配置与构建脚本的升级版本的提升还伴随着配置文件的大改动。Angular CLI 从 6.0 开始用angular.json取代了老式的.angular-cli.json结构从“单应用配置”变成“多项目配置”所有构建选项的路径都要找一遍。升级后我看到很多人卡在找不到scripts、styles配置其实重新熟悉projects.项目名.architect.build.options这个路径就能快速定位。polyfills.ts也是重灾区。老项目往往在 polyfills 里手动引入一堆浏览器垫片新版本从 Angular 15/16 开始推荐采用函数式配置Angular 18 更是把 polyfills 调整成了数组配置值直接写包名而不是布尔开关。如果你发现升级后浏览器控制台报core-js相关错误多半是 polyfills 配置没跟上优先核对官方 migration 列表里 polyfills 相关的变更。tsconfig 同样要调。Ivy 编译器要求的angularCompilerOptions和老配置不一样enableIvy这种老标志在 13 之后直接删掉strictTemplates建议打开能帮你提前发现模板里的类型错误。每次升级时 schematics 会尝试自动改这些文件但遇到自定义构建配置时自动改不干净最终还是得手工对照官方模板补齐。4. 升级测试与问题排查实录4.1 升级后的回归测试策略代码迁移完不代表升级完成测试通过才算。我的回归策略分三层第一层是构建检查ng build能在编译期把类型错误、模板错误拍死一大半第二层是单元测试把每个模块的 karma 测试跑一遍重点看依赖注入是否正常、组件渲染是否报错第三层是冒烟测试至少完整走一遍核心业务链路。这里有个经验教训unit test 在升级中的作用不只是验证“测试是否通过”还能帮你看清“错误发生的位置”。比如升级后某个测试报NullInjectorError能直接告诉你某个 service 的提供方配置没跟上这在数百个文件的大项目里尤其有价值——比手工翻代码找问题快得多。第三层冒烟测试常常被忽视但恰恰最值得做。很多升级失败的场景是编译过了、单测过了页面一打开白屏原因多半是运行时依赖注入或路由懒加载配置出了问题。所以升级后第一个手动操作就是完整走一遍主要用户路径别只看“能打开首页”就宣布升级成功。4.2 高频报错速查表我把实际升级中遇到的高频报错整理成了速查表每条都标注了原因和解决方向报错信息常见原因解决方向Cant bind to ngModel since it isnt a known propertyReactiveFormsModule 或 FormsModule 未正确导入检查模块导入新版推荐在组件 imports 中显式声明Type ObservableX is not assignable to type ObservableYRxJS 类型问题通常是操作符返回类型不匹配统一 RxJS 到 6 并改用 pipeable 操作符ExpressionChangedAfterItHasBeenCheckedError变更检测周期内修改绑定值调整生命周期钩子把数据变更前移到 ngOnInit 之前NG0203 Cant resolve all parameters for未使用Injectable或依赖无法解析给 service 加Injectable({providedIn: root})Cannot resolve symbol AppModule入口文件配置错误检查 main.ts 与 angular.json 中的应用入口ERROR NG6002 Appears in the NgModule.imports of AppModule, but could not be resolved模块导入路径失效常见于升级后目录变更清理失效导入重构时统一更新引用Zone zone.js has caught an error未正确注册 zone.js 或混用外部库改变流检查 polyfills 配置确认 zone.js 已引入碰到报错别急着改代码先看报错的完整堆栈和触发时所在的模块判断是编译期还是运行期。编译期错误优先检查 tsconfig、依赖版本、模板语法运行期错误优先检查依赖注入、路由配置、第三方库初始化。4.3 踩坑记录与独家避坑技巧整个升级过程我们踩了几次比较大的坑挑几个有代表性的分享。第一个坑是ng update自动改代码后没有及时 review。有一次升级 Angular 15 时 schematics 自动改了一批组件模板里的选择器引用但由于项目里有自定义 lint 规则改出来的代码风格不匹配构建过了、代码审查时发现了隐患。我现在的习惯是每次升级后把git diff按文件类型分组认真过一遍特别是自动改动的模板和测试文件不能因为“工具改的”就放松审查。第二个坑是升级完忘记更新 CI 里的 Node 镜像版本。开发机上一切正常一到 CI 就编译失败排查半天发现是 CI 用的还是旧 Node。建议升级前就在 CI 配置里标明项目要求的 Node 版本避免环境和代码不一致。第三个坑是版本锁定的问题。团队里有同事npm install时把依赖版本浮动到了^16.0.0导致部分机器莫名升到了 16.x和小团队里其他人用的 15.x 产生差异。升级完毕后把package.json里的依赖锁成精确版本或者完整提交package-lock.json/yarn.lock不然版本漂移会制造出一堆“我这怎么不报错”的迷惑现场。最后再单独强调一条大项目升级不要试图在一个 PR 里全做完。按模块拆分先升核心框架和基础设施再逐个模块迁移代码每个模块单独提测。这样的好处是出问题时有明确的回退边界不会让整个项目停摆。我们项目里两次大版本升级都是按这个节奏走的整体虽然拉长了周期但每个迭代都有可审视的产物团队心态也稳定得多。我个人在实际操作中的体会是Angular 升级最磨人的不是某个 API 不会改而是“你以为已经全改完的时候总有一个角落里的老写法在等着炸你”。所以心态上要做好准备这是一次系统性的技术债偿还过程不是一条命令能解决的快捷操作。每升一个版本就相当于给项目做一次体检提前发现那些隐藏的坏味道反而是升级带来的额外价值。如果你正在犹豫要不要升级我的建议很明确——尽早升小步走系统越大越要趁早处理拖到某个版本被迫断更时代价只会更大。
返回列表