Angular upgrade/static 公共 API 全解:@angular/upgrade_static 的 API 报告与混合升级应用实战
本文以 Angular 仓库中的公共 API 报告文件 index.api.md 为核心,逐条解读 @angular/upgrade_static 包的完整导出清单,并结合 upgrade/static 源码 与 upgrade 公共实现 深入剖析 UpgradeModule、UpgradeComponent、downgradeModule、downgradeComponent、downgradeInjectable 等 API 的真实行为与底层机制。读完后,你将能够独立搭建一个 AngularJS 与 Angular 共存的混合应用,理解两套框架之间变更检测的双向同步原理,并知道如何利用 API 报告文件校验包的公共 API 表面是否发生破坏性变更。
API 报告文件是什么:@angular/upgrade_static 的黄金标准
index.api.md 位于仓库的 goldens/public-api/upgrade/static/ 目录下,文件开头声明:
Do not edit this file. It is a report generated by API Extractor.
这是一份由 API Extractor 工具自动生成的 公共 API 报告(API Report)黄金文件。仓库在 CI 中会重新生成报告并与该文件比对,任何未被显式批准的公共 API 增删改(新增导出、参数变化、废弃标记等)都会导致比对失败,从而保证 @angular/upgrade_static 包的对外 API 表面(public API surface)稳定可控。这也是 goldens/README.md 所描述的 goldens 目录的职责:为发布产物提供不可手改的基线。
该报告完整列出了 @angular/upgrade_static 的全部 @public 导出:
| 导出项 | 类型 | 作用 |
|---|---|---|
downgradeComponent(info) |
函数 | 将 Angular 组件降级为 AngularJS 指令,供 AngularJS 模板使用 |
downgradeInjectable(token, downgradedModule?) |
函数 | 将 Angular 服务(可注入对象)降级为 AngularJS 服务 |
downgradeModule(moduleOrBootstrapFn) |
函数(两个重载,其中一个已废弃) | 创建一个 AngularJS 模块,可"按需/惰性"引导一个 Angular 模块(Lite 模式) |
getAngularJSGlobal() / setAngularJSGlobal(ng) |
函数 | 获取/设置全局 window.angular 对象引用 |
UpgradeComponent |
类(可继承) | 用作 Angular 指令基类,将 AngularJS 组件"升级"为可在 Angular 模板中使用的组件 |
UpgradeModule |
类(NgModule) | 提供 AngularJS 核心服务的 Angular 模块,并持有 bootstrap() 引导混合应用 |
VERSION |
常量 | 包的 Version 元数据 |
报告中的每一条签名(例如 downgradeModule 的 Type<T> | ((extraProviders: StaticProvider[]) => Promise<NgModuleRef<T>>) 重载、UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy 的生命周期接口)都可以直接对照仓库源码验证:
- 公共导出入口:public_api.ts,其中
downgradeComponent、downgradeInjectable、VERSION、getAngularJSGlobal/setAngularJSGlobal转发自 upgrade 包的 common 层,而downgradeModule、UpgradeComponent、UpgradeModule则实现于 static/src/; - 入口文件 index.ts 仅 re-export
public_api,注释明确说明该文件在 AOT 构建时会被ngc用生产版 index.ts 替换以重写私有符号名。
心智模型:两个框架如何共存于一个页面
UpgradeModule 的 JSDoc 给出了理解混合应用最重要的"心智模型"(见 upgrade_module.ts):
- 应用内运行着两个相互独立的框架,每个框架把对方当作黑盒;
- 页面上的每个 DOM 元素恰好由一个框架"拥有"——谁实例化了它,谁就负责更新它,另一个框架对其视而不见;
- AngularJS 指令永远在 AngularJS 框架代码中执行,Angular 组件永远在 Angular 框架代码中执行,与实例化位置无关;
- "升级"(upgrade)指用 Angular 指令包裹 AngularJS 组件,即
UpgradeComponent;"降级"(downgrade)指用 AngularJS 指令包裹 Angular 组件,即downgradeComponent; - 升级/降级组件实例化时,宿主元素归"做实例化的框架"所有,而组件视图归另一个框架所有。因此绑定语义遵循实例化框架的规则,但模板绑定语法始终使用 Angular 风格(如方括号属性绑定);
- Angular 先引导,AngularJS 后引导,且 AngularJS 始终拥有应用根组件;
- 应用运行在 Angular 的 zone 中,因此不再需要手动调用
$apply()。
upgrade/static 的 static 一词来源于 AOT(Ahead-of-Time)支持:所有辅助 API 都是设计为可以在编译期静态分析的,这正是它与旧版运行时 @angular/upgrade 的本质区别。
UpgradeModule:引导混合应用的核心
结构与提供的服务
UpgradeModule 本身是一个 NgModule,装饰器为(见 upgrade_module.ts):
@NgModule({providers: [angular1Providers, ɵinternalProvideZoneChangeDetection({})]})
export class UpgradeModule {
public $injector: any; // 升级应用的 AngularJS $injector
public injector: Injector; // Angular 注入器(NgAdapterInjector 包装)
public ngZone: NgZone; // 引导区
constructor(
injector: Injector, // 升级应用的根 Injector
public ngZone: NgZone,
private platformRef: PlatformRef, // 用于把 AngularJS 应用生命周期绑到 PlatformRef
) { ... }
}
导入该模块即向根注入器注入四个 AngularJS 核心服务(实现见 angular1_providers.ts):
export const angular1Providers = [
{provide: '$injector', useFactory: injectorFactory, deps: []},
{provide: '$rootScope', useFactory: rootScopeFactory, deps: ['$injector']},
{provide: '$compile', useFactory: compileFactory, deps: ['$injector']},
{provide: '$parse', useFactory: parseFactory, deps: ['$injector']},
];
这里有一处精巧实现:AngularJS 的 $injector 在引导之前还不存在,因此源码用模块级变量 tempInjectorRef 暂存,引导完成后由 setTempInjectorRef() 注入,再经由 provider 的一次 get 触发读取并清空引用以防内存泄漏(angular1_providers.ts#L11-L27)。
bootstrap() 的完整流程
API 报告中 UpgradeModule.bootstrap(element, modules?, config?) 对应源码中的同名方法(upgrade_module.ts#L183-L364):
- 参数:
element为引导目标元素;modules(默认[])是要引导的 AngularJS 模块名数组;config是可选的 AngularJS bootstrap 配置,透传给angular.bootstrap(); - 返回值:
angular.bootstrap()的返回值(一个带$destroy等方法的 scope)。
其内部步骤值得逐一看:
- 创建名为
ngUpgrade.init的内部 AngularJS 模块,注册UPGRADE_APP_TYPE_KEY常量为Static(这是后续区分UpgradeModule模式与downgradeModuleLite 模式的依据),并把 Angular 的Injector作为INJECTOR_KEY值供 AngularJS 侧读取; - 在
.config中装饰$testability:将 Angular 侧Testability.whenStable与 AngularJS 侧串联,只有两边都稳定时才回调——这是混合应用测试可靠等待稳定状态的保障(相关测试见 testability_spec.ts); - 装饰
$interval:让setInterval在 NgZone 之外调用、而回调在 zone 内执行,这样$interval不会阻塞 zone 稳定状态,保持 AngularJS 的原始行为; - 在
.run钩子中:保存$injector、把 Angular 注入器挂到 DOM 元素上(以便 AngularJS 侧require)、注册platformRef.onDestroy(() => destroyApp($injector))——即 AngularPlatformRef销毁时一并销毁 AngularJS 应用(源码注释指明这主要服务于 HMR 等需要销毁应用的场景,对应上游 issue #39935); - 双向变更检测桥接:在下一个宏任务中订阅 zone 的微任务空闲事件(ZoneJS 场景为
ngZone.onMicrotaskEmpty;从源码结构看,若检测到NoopNgZone——即 zoneless——则退化为订阅ApplicationRef.afterTick,源码注释坦诚这是因混合应用缺乏 zoneless 覆盖而做的防御性兼容),每次 zone 稳定后触发$rootScope.$digest()(若已有 digest 进行中则改为$evalAsync()并发出警告);$rootScope.$destroy时取消订阅。
const subscription =
this.ngZone instanceof ɵNoopNgZone
? (this.applicationRef as any).afterTick.subscribe(() => synchronize())
: this.ngZone.onMicrotaskEmpty.subscribe(() => synchronize());
$rootScope.$on('$destroy', () => {
subscription.unsubscribe();
});
- 组装最终的
ngUpgrade模块(依赖ngUpgrade.init与用户传入的modules),在ngZone.run()内调用angular.bootstrap(),并打补丁让angular.resumeBootstrap也运行在 zone 内,最终返回angular.bootstrap的返回值。
UpgradeComponent:把 AngularJS 组件带入 Angular 模板
API 报告显示 UpgradeComponent 实现了 OnInit, OnChanges, DoCheck, OnDestroy 四个生命周期接口,构造签名为 constructor(name: string, elementRef: ElementRef, injector: Injector)(upgrade_component.ts)。
使用方式
假设有一个 AngularJS 组件 ng1Hero(带 @、=、& 绑定的 scope),要让它出现在 Angular 模板中,需要派生一个 Angular 指令:
@Directive({selector: 'ng1-hero'})
export class Ng1HeroCmp extends UpgradeComponent {
hero: string; // 对应 '@'/'<' 绑定(input)
@Output() nameChange = new EventEmitter(); // 对应 '=' 绑定
save: () => void; // 对应 '&' 绑定(output)
constructor(elementRef: ElementRef, injector: Injector) {
super('ng1Hero', elementRef, injector); // 第一个参数是 AngularJS 侧的指令名
}
}
AOT 编译器要求选择器、inputs、outputs 在编译期静态可见,因此必须写 @Directive() 装饰器并显式声明绑定,而不能在运行时动态生成。
生命周期如何映射到 AngularJS 钩子
从 upgrade_component.ts#L127-L234 的实现看,四个 Angular 钩子分别驱动了 AngularJS 侧的对应行为:
- 构造时:通过内部
UpgradeHelper获取 AngularJS 侧的IDirective定义、把宿主要素包成$element,依据directive.scope(或bindToController对象)调用$parentScope.$new(isolate)创建组件 scope,并对=、&绑定预创建EventEmitter输出(=绑定映射为propChange输出名,&绑定映射为同名输出); - ngOnInit:准备模板与投影(
prepareTransclusion/compileTemplate)、实例化 controller、处理bindToController、回放暂存的pendingChanges(首次ngOnChanges早于 controller 构建时会被缓存),依次调用 AngularJS 的$onChanges→$onInit→$doCheck(通过父 scope$watch持续触发)→ pre/post link →$postLink; - ngOnChanges:把
SimpleChanges中的当前值转发到绑定目标(scope 或 controller 实例),并调用 AngularJS 侧的$onChanges; - ngDoCheck:轮询所有
=双向绑定属性,用Object.is比对新旧值,有变化则通过对应EventEmitter向外发值(这就是=绑定在 Angular 侧表现为输出事件的机制); - ngOnDestroy:注销
$doCheckwatcher,销毁 scope 与 controller 实例。
一个重要的限制值得注意:scope 与 bindToController 不能同时定义绑定,否则抛出 Binding definitions on scope and controller at the same time is not supported.(upgrade_component.ts#L236-L242)。
downgradeComponent:把 Angular 组件带入 AngularJS 模板
downgradeComponent(info) 在 API 报告中的签名为:
export function downgradeComponent(info: {
component: Type<any>;
downgradedModule?: string;
propagateDigest?: boolean;
inputs?: string[]; // v4 起废弃,不再使用
outputs?: string[]; // v4 起废弃,不再使用
selectors?: string[]; // v4 起废弃,不再使用
}): any;
实现位于 downgrade_component.ts。它返回一个 AngularJS 指令工厂函数(带 $inject = ['$compile', '$injector', '$parse'] 标注),你把它注册到 AngularJS 模块即可:
angular.module('ng1App', [ng2Module])
.directive('ng2Heroes', downgradeComponent({component: Ng2Heroes}));
参数含义(结合源码与 JSDoc):
component(必填):要降级的 Angular 组件Type;downgradedModule(可选):组件"所属"的downgradeModule()返回的模块名。仅在降级多个 Angular 模块时需要,用于决定实例化组件时引导哪个 Angular 模块、从哪个 injector 取服务;propagateDigest(可选,默认true):是否每次$digest都在组件上执行detectChanges。设为false时,仅当组件 inputs 变化才触发变更检测;inputs/outputs/selectors:自 v4 起废弃且源码中已不使用(绑定信息现在直接从组件元数据静态获取),报告文件仍保留这些可选字段以维持签名兼容。
生成的指令具有 restrict: 'E'、terminal: true、require: [REQUIRE_INJECTOR, REQUIRE_NG_MODEL](即宿主元素上同时支持 AngularJS 侧 require 注入器与 ng-model 双向绑定桥接)。其 link 函数内部处理了三种注入器拓扑(顶层组件、同模块父组件、跨模块父组件),通过 DowngradeComponentAdapter 完成组件创建与 ng-model/输入输出的双向同步;Lite 模式下还会额外确保所有回调用 NgZone.run 包回 Angular zone(downgrade_component.ts#L109-L124)。
downgradeInjectable:把 Angular 服务带入 AngularJS
downgradeInjectable(token, downgradedModule?) 实现见 downgrade_injectable.ts。它返回一个只依赖 $injector 的工厂函数,典型用法:
angular.module('ng1App', [ng2Module])
.factory('ng2HeroesService', downgradeInjectable(Ng2HeroesService));
随后在 AngularJS controller 中即可通过注入 'ng2HeroesService' 拿到 Angular 侧的服务实例。两个使用要点(来自源码 JSDoc 的重要告警):
- 该服务必须先被提供在升级应用的某个
NgModule中; - 使用
downgradeModule()时,降级注入对象在其 Angular 模块被实例化之前不可用——只能在"保证模块已实例化"的范围内使用,例如只能用于同一模块内的已降级组件,不能用于可能独立于 Angular 使用的 AngularJS 组件。多模块场景下必须通过downgradedModule参数指定归属模块,源码会用validateInjectionKey()校验 key 存在并抛出清晰的错误(Trying to get the Angular injector before bootstrapping the corresponding Angular module.等)。
downgradeModule:Lite 模式的惰性引导
API 报告中 downgradeModule 有两个重载:
export function downgradeModule<T>(moduleOrBootstrapFn: Type<T> | ((extraProviders: StaticProvider[]) => Promise<NgModuleRef<T>>)): string;
// @public @deprecated
export function downgradeModule<T>(moduleOrBootstrapFn: NgModuleFactory<T>): string;
第二个 NgModuleFactory 重载已被标记 @deprecated(报告文件中以 // @public @deprecated 注释标注),应传 NgModule 类或引导函数。实现见 downgrade_module.ts#L373-L452:
- 传入
NgModule类:包装为platformBrowser(extraProviders).bootstrapModule(...); - 传入 函数:函数接收一组额外
StaticProvider,返回解析为NgModuleRef的 Promise——这允许完全自定义惰性引导逻辑(例如先import()再bootstrapModule); - 返回值是生成的 AngularJS 包装模块名(形如
ngUpgrade.lazy1,内部自增moduleUid),把它声明进主 AngularJS 模块即可。
内部机制上,它注册了一个名为 ngUpgrade.init 的兄弟模块:标记 UPGRADE_APP_TYPE_KEY 为 Lite、惰性工厂 LAZY_MODULE_REF 在首次被取用(即某个已降级组件/注入对象被实例化时)才执行 bootstrapFn,把结果包装为 NgAdapterInjector 并同样注册 PlatformRef.onDestroy(() => destroyApp($injector));还在 .config 中通过 DOWNGRADED_MODULE_COUNT_KEY 记录已降级模块数量,供 downgradeComponent 判断是否处于"多降级模块"模式。
与 UpgradeModule 的关键差异
源码文档明确警告:同一个混合应用不能同时使用 downgradeModule() 与 UpgradeModule,只能二选一。两者的行为差异有两点(downgrade_module.ts#L93-L111):
downgradeModule()不在 Angular zone 内引导主 AngularJS 模块(UpgradeModule.bootstrap()则在 zone 内调用angular.bootstrap);downgradeModule()不会自动把 Angular 侧的变更同步为$digest()——它只在确定必要时(如已降级组件的输入变化)触发对方框架的变更检测。
这意味着 UpgradeModule 以更多变更检测运行换取"两边总被正确通知"的省心;downgradeModule 模式则把性能换给开发者,需要手动用 scope.$apply() / $rootScope.$digest() 触发 AngularJS 侧变更检测,或用 ngZone.run(...) 触发 Angular 侧。对于变更检测密集的应用,Lite 模式通常更省,代价是心智负担。
降级多个模块时的注意事项:每个降级组件/注入对象必须显式通过 downgradedModule 关联到某个模块;bootstrapModule()/bootstrapModuleFactory() 引导的每个模块都被视为"根"模块,providedIn: 'root' 的服务在每个模块中都会新建实例——如需共享,可建一个共享根模块并用它的 injector 引导其余模块(相关测试见 downgrade_module_spec.ts)。
getAngularJSGlobal / setAngularJSGlobal 与 VERSION
报告中的 getAngularJSGlobal(): any 与 setAngularJSGlobal(ng: any): void 实现在 upgrade 包 common 层的 angular1.ts:它们负责读取/替换全局 window.angular 对象引用。混合应用升级期间常需在多个 AngularJS 版本或 mock 之间切换全局对象,这两个函数提供了可控的访问入口;UpgradeModule.bootstrap() 中直接使用 (window as any)['angular'] 打补丁 resumeBootstrap(upgrade_module.ts#L344-L361)也依赖同一全局对象的稳定性。
VERSION: Version 则是标准的包版本常量(转发自 common/src/version.ts),供运行时诊断与调试使用。
测试与验证:从仓库中继续深入
本 API 报告所描述的每个行为都有对应集成测试佐证,可作为深入阅读的入口:
- upgrade_module_spec.ts:
UpgradeModule引导、生命周期绑定; - change_detection_spec.ts:两种模式下双向变更检测的桥接行为;
- downgrade_component_spec.ts / upgrade_component_spec.ts:组件降级/升级的绑定映射;
- injection_spec.ts:
downgradeInjectable与跨注入器取值; - testability_spec.ts:
$testability装饰后双方whenStable的串联语义。
小结
index.api.md 以 API Extractor 黄金报告的形式固化了 @angular/upgrade_static 的全部公共契约:UpgradeModule + UpgradeComponent 构成"Angular 引导、自动双向同步变更检测"的完整升级路径;downgradeModule + downgradeComponent + downgradeInjectable 构成"AngularJS 引导、按需惰性加载 Angular 模块"的 Lite 性能路径;两组模式互斥,需按应用的性能画像选择。对照 packages/upgrade/static/ 与 packages/upgrade/src/common/ 的源码,可以逐行验证报告中的每一条签名——这正是黄金 API 报告文件"报告即契约、契约随源码"的价值所在。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00