首页
/ Angular 迁移全攻略:从 NgModule 到 Standalone 与 Signal API 的 14 条官方自动化升级路线

Angular 迁移全攻略:从 NgModule 到 Standalone 与 Signal API 的 14 条官方自动化升级路线

2026-09-07 09:29:40作者:曹令琨Iris

Angular 持续迭代的过程中引入了 Standalone 组件、内置控制流语法、基于 Signal 的输入输出与查询 API 等新一代范式,而官方将对应的一整套"自动化迁移机制"集中收录在本文档站点目录(adev/src/content/reference/migrations/overview.md)中。本文以该迁移总览为主体,逐条拆解其收录的全部 14 条官方迁移(migration)指令:它们都以 @angular/core 集合提供的 schematics 形式存在,允许你在不产生破坏性变更的前提下,对既有项目进行增量式升级。读完本文,你将掌握每条迁移的命令、可配置项、边界条件与推荐执行顺序,并能对照仓库源码确认这些迁移的实现位置,从而为旧项目规划出一条从 NgModule 平滑走向现代 Angular 的完整升级路线图。

迁移总览:14 条官方路线图

在迁移前先把握全局。下表汇总了 overview 中 14 张迁移卡片的对应 schematics 命令与核心目标,各命令均由 ng generate @angular/core:<schematic> 的形式调用:

迁移方向 命令 解决的核心问题
Standalone 组件 ng generate @angular/core:standalone 将组件/指令/管道转为 standalone,移除多余 NgModule,改用独立引导 API
内置控制流语法 ng generate @angular/core:control-flow @if/@for/@switch 替代 *ngIf/*ngFor/*ngSwitch
inject() 函数 ng generate @angular/core:inject 将构造函数注入改为函数式注入
路由懒加载 ng generate @angular/core:route-lazy-loading 把急加载组件路由转为 loadComponent 懒加载
Signal input() ng generate @angular/core:signal-input-migration @Input 字段迁移到 signal input API
Signal output() ng generate @angular/core:output-migration @Output/EventEmitter 迁移到 output() 函数
Signal 查询 ng generate @angular/core:signal-queries-migration @ViewChild/@ContentChild 等迁移到 signal queries
清理无用导入 ng generate @angular/core:cleanup-unused-imports 清理组件 imports 数组中未被模板使用的符号
自闭合标签 ng generate @angular/core:self-closing-tag 模板改写为自闭合标签
NgClass → class 绑定 ng generate @angular/core:ngclass-to-class [class]/[class.x] 绑定替代 NgClass
NgStyle → style 绑定 ng generate @angular/core:ngstyle-to-style [style] 绑定替代 NgStyle
RouterTestingModule 迁移 ng generate @angular/core:router-testing-module-migration 测试中改用 RouterModuleprovideLocationMocks()
CommonModule → 独立导入 ng generate @angular/core:common-to-standalone 用最小化的指令/管道导入替代整包 CommonModule

这些 schematics 都注册在仓库的 @angular/core 迁移集合中,可在 packages/core/schematics/collection.json 中逐条确认其定义与别名(例如 standalonecontrol-flowinject 等)。对应每个迁移的实现源码位于 packages/core/schematics/ng-generatepackages/core/schematics/migrations 目录,源码结构基本与上表一一对应。

迁移执行前的通用前置条件

多数迁移文档都强调以下共同前提(详见 standalone.md 的 "Before updating" 一节,cleanup-unused-imports.md 等亦有类似说明):

  1. 项目版本满足要求:例如 standalone 迁移要求 Angular 15.2.0 及以上;内置控制流语法自 Angular v17 起可用;signal inputs/queries 与 output() 函数被认为在 v19 达到生产就绪。请结合自己项目的版本确认迁移可用性。
  2. 项目必须无编译错误:迁移依赖对代码的静态分析,编译报错会导致 Angular 无法正确识别待迁移代码。
  3. 工作在干净的分支上且所有改动已保存:迁移会改写大量文件,干净的 Git 工作区是出错后可回退的前提。

此外,从 overview 各卡片与迁移文档可以提炼出三条贯穿始终的工程纪律

  • 分步执行、逐步验证:每运行一步迁移后,都应重新构建并运行测试,确认无回归后再进入下一步;
  • 保留人工复核环节:自动迁移无法覆盖所有边界情况,迁移产物可能不符合你项目的格式化规范,需配合 lint/format 工具收尾;
  • 利用 --path 做增量迁移:几乎所有迁移都支持 path 选项,让你可以先对子目录试点,再推广到全项目。

第一梯队:Standalone 与打包体积优化

迁移到 Standalone:三步走流程

Standalone 组件允许组件、指令、管道直接在自身声明依赖,而无需经由 NgModule,从而大幅简化 Angular 应用的作者体验。官方提供了 standalone schematic(详见 standalone.md)自动化完成大部分改造,其迁移过程由三个模式(mode)构成,必须按以下顺序依次执行,并在每一步之间验证构建:

ng generate @angular/core:standalone

模式一:Convert declarations to standalone —— 将所有组件、指令和管道转换为 standalone:移除 standalone: false,并把依赖补充进各自的 imports 数组。迁移会跳过引导(bootstrap)组件的 NgModule(因为它们通常还是 bootstrapModule 风格的根模块),这些会在模式三中被一并处理。一个典型例子:转换前 Greeter 组件声明了 standalone: false 并使用 *ngIf,而共享模块 SharedModule 通过 imports: [CommonModule] 间接提供 NgIf;转换后 Greeter 变为 standalone 并在 imports: [NgIf] 中直接导入所需指令。

模式二:Remove unnecessary NgModules —— 在全部声明转换为 standalone 后,删除可以安全移除的 NgModule。迁移判定一个模块可安全删除的条件是:无 declarations、无 providers、无 bootstrap 组件、无引用 ModuleWithProviders 或不可删除模块的 imports,且无类成员(空构造函数会被忽略)。若某处引用无法自动删除,迁移会留下如下 TODO 注释供人工清理:

/* TODO(standalone-migration): clean up removed NgModule reference manually */

模式三:Switch to standalone bootstrapping API —— 将 bootstrapModule 用法改写为基于 standalone 的 bootstrapApplication,移除根组件上的 standalone: false 并删除根 NgModule;若根模块带 providersimports,迁移会尽量把这些配置拷贝进新的引导调用中。转换前 main.ts 里的 platformBrowser().bootstrapModule(AppModule) 会被改写为:

import {bootstrapApplication} from '@angular/platform-browser';
import {App} from './app';

bootstrapApplication(App).catch((e) => console.error(e));

完成以上三步后(详见原文档 "After the migration" 部分),建议继续:手动清理剩余的 NgModule 声明、跑一遍单元测试并修复失败用例、执行格式化器与 linter(部分 linter 支持 --fix 自动修复)。

常见失败原因与限制(见 standalone.md 的 "Common problems" 与 "Limitations"):编译错误会让迁移无法正确分析;schematic 只分析 tsconfig.json 捕获到的文件;无法静态分析元数据的类会被跳过。此外,单测代码不经 AoT 编译,为测试组件补的 imports 可能不完整;schematic 只识别对 Angular API 的直接调用,无法识别诸如自定义封装 TestBed.configureTestingModule 的包装函数。

移除整包 CommonModule:拆分为最小导入

该迁移帮助项目将组件中对 CommonModule 的整包导入替换为模板实际用到的最小指令与管道集合(如 NgIfNgForAsyncPipeJsonPipe 等),详见 common-to-standalone.md

ng generate @angular/core:common-to-standalone

例如模板中同时使用了 *ngIfasync 管道与 json 管道时,迁移会把 imports: [CommonModule] 改写为 imports: [AsyncPipe, JsonPipe, NgIf] 并移除 CommonModule 导入。该迁移同样支持 --path 选项增量执行(默认作用于项目根 ./)。这一收窄导入的做法既配合了控制流语法的推行,也是降低打包体积、提升 tree-shaking 效果的重要一步。

路由懒加载迁移:按需拆包

route-lazy-loading.md 描述的迁移将急加载的组件路由转为懒加载,使构建产物被拆分成更小的 chunk,从而减少首屏加载的 JS 体积:

ng generate @angular/core:route-lazy-loading

迁移会扫描以下方式定义的路由并执行转换:RouterModule.forRoot / RouterModule.forChildRouter.resetConfigprovideRouter,以及类型为 RoutesRoute[] 的变量。对于 standalone 且急加载的组件,迁移将其改写为 loadComponent

// 转换前
RouterModule.forRoot([{path: 'home', component: Home}]),
// 转换后
RouterModule.forRoot([{path: 'home', loadComponent: () => import('./home').then((m) => m.Home)}]),

若路由中某些组件仍声明在 NgModule 里(非 standalone),迁移会输出这些组件的清单及所在文件位置,提示你先用 standalone 迁移(见上文)将其转换后再次运行本迁移。该迁移支持 --path src/app/sub-component 限定迁移范围,path 值为项目内的相对路径。

模板写法现代化三件套

三条轻量级模板迁移聚焦于减少运行时指令与冗余导入:

自闭合标签self-closing-tags.md):Angular 模板自 v16 起支持自闭合标签,运行 ng generate @angular/core:self-closing-tag 可将 <hello-world></hello-world> 转换为 <hello-world />

NgClass → class 绑定ngclass-to-class.md):运行 ng generate @angular/core:ngclass-to-class 仅迁移被判定为安全的用法,例如将 <div [ngClass]="{admin: isAdmin, dense: density === 'high'}"></div> 改写为 <div [class]="{admin: isAdmin, dense: density === 'high'}"></div>。默认情况下,对象字面量键含空格分隔类名的用法不会被迁移;开启 --migrate-space-separated-key 后,迁移会为每个键单独生成一条绑定,例如 {'class1 class2': condition} 会被拆成两条 [class.class1][class.class2] 绑定。

NgStyle → style 绑定ngstyle-to-style.md):运行 ng generate @angular/core:ngstyle-to-style[ngStyle] 迁移为 [style] 绑定。默认只迁移内联对象字面量;开启 --best-effort-mode 后,绑定到对象引用的写法(如 [ngStyle]="styleObject")也会被迁移——但若该对象后续被原地修改,这可能不安全,因此该选项默认关闭。

控制流语法迁移:告别 CommonModule 的模板糖

自 Angular v17 起,内置控制流语法 @if@for@switch 被编译进模板本身,不再需要为了 *ngIf*ngFor*ngSwitch 而导入 CommonModule(详见 control-flow.md):

ng generate @angular/core:control-flow

该迁移会改写应用中所有既有模板代码。值得注意的是它带来的一个破坏性差异:在 @fortrack 表达式中,若被跟踪的属性变化而对象引用未变(就地修改),Angular 只会更新视图绑定(含组件输入),而不会销毁重建元素;与之相对,*ngFor 在同场景下若 trackBy 返回了不同的值,则会执行元素的重新挂载(destroy + recreate)。迁移后若应用的渲染行为依赖旧的销毁重建语义,需要据此人工审视相关逻辑。

第二梯队:Signal 化改造(input/output/queries)

从 Angular v17/v19 起逐步引入并定为生产就绪的 Signal 化组件 API,让开发者不再依赖装饰器,即可获得更精确的类型与更细粒度的变更检测能力。

迁移到 Signal input()

signal inputs 在 v19 被认定生产就绪(signal-inputs.md)。命令行迁移方式:

ng generate @angular/core:signal-input-migration

迁移会做两件事:① 把 @Input() 类成员改写为 input() 等价物;② 同步更新对已迁移输入的所有引用——包括模板、host bindings 与 TypeScript 代码。例如 @Input() name: string | undefined = undefined; 会变为 readonly name = input<string>();,模板中 {{ name ?? '' }} 相应变为 {{ name() ?? '' }},方法内对 this.name 的判空访问也需要改为先取信号值再判空。

配置选项:

  • --path:默认更新整个 Angular CLI 工作区,可限定到子目录;
  • --best-effort-mode:默认会跳过无法安全迁移的输入;开启后尽量迁移所有输入,即使可能破坏构建;
  • --insert-todos:为被跳过的输入添加带有原因说明的 TODO 注释(例如"应用代码写入了该输入,导致无法迁移");
  • --analysis-dir@Input() 迁移需要更新所有受影响引用,因此默认会分析整个工作区(与 --path 无关);在大项目中可用该选项把分析范围收窄到子目录,但目录外的引用会被静默跳过,可能破坏构建。

迁移到 output() 函数

output API 于 v17.3 引入、v19 生产就绪,它模仿 input() API 形态但并非基于 Signaloutputs.md)。执行:

ng generate @angular/core:output-migration

迁移完成三件事:① @Output() 类成员改写为 output() 等价物;② 更新文件顶层的 TypeScript 模块导入;③ 将不推荐的 event.next() 调用改为 event.emit(),并删除 event.complete() 调用。例如 @Output() someChange = new EventEmitter<string>(); 变为 readonly someChange = output<string>();

有一类例外不会被迁移:当事件与 pipe() 方法组合使用时,迁移会跳过该代码(例如类中同时存在 this.close.complete()this.close.pipe() 的写法)。配置项与 signal input 迁移一致,均支持 --path(未指定时会提示你输入路径并默认处理整个工作区)与 --analysis-dir,用法如:

ng generate @angular/core:output-migration --path src/app/sub-folder

迁移到 Signal queries

signal queries(viewChild/contentChild 等)同样于 v19 被认定生产就绪(signal-queries.md)。执行:

ng generate @angular/core:signal-queries-migration

迁移把 @ViewChild()@ViewChildren@ContentChild@ContentChildren 类成员改写为对应的 signal 版本,并同步更新模板、host bindings 与 TypeScript 中对这些查询的所有引用。例如 @ContentChild('someRef') ref: ElementRef | undefined = undefined; 变为 readonly ref = contentChild<ElementRef>('someRef');,模板中相应改为调用 someRef()。配置选项(--path--best-effort-mode--insert-todos--analysis-dir)的语义与 signal input 迁移完全一致,被跳过的查询同样会附带如"应用代码写入了该查询"之类的 TODO 说明。

在 VSCode 中一键执行 Signal 迁移

signal input 与 signal queries 两条迁移除命令行外,还以 VSCode 代码重构动作(code refactor action) 的形式提供(详见两篇文档的 "VSCode extension" 一节)。安装最新版 Angular Language Service 扩展后,将光标点在某个 @Input 字段(或组件/指令上),稍候点击出现的黄色灯泡按钮,即可在菜单中选择对应的 signal 迁移;signal queries 的触发对象则是 @ViewChild@ViewChildren@ContentChild@ContentChildren 字段。下面的截图展示了这一交互形态(图片位于 adev/src/assets/images/migrations/signal-inputs-vscode.pngadev/src/assets/images/migrations/signal-queries-vscode.png)。

Screenshot of the VSCode extension and clicking on an @Input field

Screenshot of the VSCode extension and clicking on an @ViewChild field

第三梯队:依赖注入与代码卫生

迁移到 inject() 函数

inject() 相比构造函数注入能提供更精确的类型,且与标准装饰器有更好的兼容性(inject-function.md)。执行:

ng generate @angular/core:inject

迁移会把构造函数注入改写为字段初始化式的 inject() 调用,例如:

// 转换前
constructor(
  private service: MyService,
  @Inject(DI_TOKEN) @Optional() readonly token: string,
) {}

// 转换后
private service = inject(MyService);
readonly token = inject(DI_TOKEN, {optional: true});

该迁移提供了四个精细配置项以定制输出:

  • path:指定待迁移的项目子路径,传 . 或留空则迁移整个目录;
  • migrateAbstractClasses:Angular 并不校验抽象类参数的注入性,因此迁移默认跳过抽象类以避免引入破坏;开启后可以迁移它们,但可能需要人工修复部分编译错误
  • backwardsCompatibleConstructors:默认迁移会尽量精简代码,包括删除构造函数参数乃至整个空构造函数。但当带 Angular 装饰器的类之间发生继承时,这可能引发编译错误。开启该选项后,迁移会额外生成一个向后兼容的构造函数签名(如 constructor(...args: unknown[]); 与空的 constructor() {} 并存),代价是代码量增加;
  • nonNullableOptional:带 @Optional 的参数注入失败时 Angular 会返回 null,因此其真实类型应含 | null;但装饰器无法影响类型,既有代码的类型往往并不正确。inject() 修正了这一点,却可能暴露出新的编译错误。开启该选项后,迁移会在 inject() 调用后追加非空断言 ! 以匹配旧类型——但已声明为可空类型的参数不会追加断言,因为依赖它的代码通常已正确处理空值。

清理无用导入

自 v19 起,Angular 会报告组件的 imports 数组中含有未在模板中使用到的符号(cleanup-unused-imports.md)。运行下述 schematic 即可清理项目中全部无用导入:

ng generate @angular/core:cleanup-unused-imports

例如当组件模板仅渲染 'Hello' 却导入了 UnusedDirective 时,迁移会移除该导入并让 imports 数组留空,保持组件声明最小化。

测试侧的 RouterTestingModule 迁移

在测试代码中,RouterTestingModule 正逐步被淘汰(router-testing-module-migration.md)。该迁移将测试中的 RouterTestingModule 用量改写为 RouterModule;当测试从 @angular/common/testing 导入 SpyLocation 并使用了其 urlChanges 属性时,迁移还会自动补充 provideLocationMocks() 以保持原有行为:

ng generate @angular/core:router-testing-module-migration

示例 1(保留路由选项):RouterTestingModule.withRoutes(routes, {initialNavigation: 'enabledBlocking'}) 会被改写为 RouterModule.forRoot(routes, {initialNavigation: 'enabledBlocking'})

示例 2(补上 location mocks):当测试通过 TestBed.inject(SpyLocation) 注入并断言 spy.urlChanges 时,迁移会在 TestBed.configureTestingModuleproviders 中加入 provideLocationMocks(),确保注入 SpyLocation 后其模拟定位能力依然可用。该迁移同样支持 --path(相对项目根的路径,默认 ./)做增量迁移。

推荐的端到端升级路径

结合 overview 对全部迁移的介绍以及各文档强调的依赖关系,对于尚停留在 NgModule + 装饰器风格的老项目,一条低风险、可逐步验证的推荐升级顺序如下:

  1. 先做行为等价、低风险的模板卫生改造:自闭合标签、NgClass → classNgStyle → style、清理无用导入——这些迁移几乎不改变运行时语义,适合热身并提前发现编译问题;
  2. 再推行 Standalone 化的三步迁移:从"convert declarations"到"remove NgModules"再到"bootstrapApplication",这是后续几乎所有优化的前置条件;
  3. 随后做打包优化:在组件已 standalone 的前提下,运行路由懒加载迁移拆分产物;同时用 common-to-standalone 收敛 CommonModule 的整包导入;
  4. 然后开启控制流语法迁移:替换 *ngIf/*ngFor/*ngSwitch,注意 @for 的 track 与视图复用语义差异;
  5. Signal 化改造作为收尾:按 signal input、signal output、signal queries、inject() 的顺序推进,配合各迁移的 --analysis-dir 与 VSCode 重构动作在大项目中分片完成;
  6. 最后清理测试与收尾:运行 router-testing-module-migration 修正测试依赖,再执行 lint、格式化与完整测试套件。

整个过程中请始终遵循文档强调的纪律:每一步迁移之间都验证构建与运行、为无法自动处理的边界保留人工复核、并把每个 schematics 的具体行为与限制对照 packages/core/schematics/collection.json 以及 packages/core/schematics/ng-generatepackages/core/schematics/migrations 目录下的实现源码来确认。这样逐级推进,你的项目就能在不推翻重写的前提下,平滑完成从旧范式到现代 Angular 的全部升级。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388