首页
/ Angular @angular/platform-browser/animations/async:provideAnimationsAsync 异步动画提供器 API 详解

Angular @angular/platform-browser/animations/async:provideAnimationsAsync 异步动画提供器 API 详解

2026-09-07 15:33:16作者:冯爽妲Honey

本文以 Angular 仓库中 goldens/public-api/platform-browser/animations/async/index.api.md 这份 API 报告文件为核心,结合 packages/platform-browser/animations/async 下的源码实现与测试用例,系统讲解 @angular/platform-browser/animations/async 包的唯一公开 API provideAnimationsAsync():它的函数签名与参数取值、返回的 EnvironmentProviders 内部结构、动画模块懒加载的底层渲染器切换机制,以及该 API 在 v20.2 中的弃用状态与迁移方向。读完后你可以完整掌握如何在 bootstrapApplication 场景下启用异步动画,并能读懂其源码级工作原理与失败回退策略。

一、API 报告文件:包的完整公开面

goldens/public-api/platform-browser/animations/async/index.api.md 是一份由 API Extractor 自动生成的 API 报告(文件开头明确标注 "Do not edit this file. It is a report generated by API Extractor."),它记录了 @angular/platform-browser_animations_async 包的完整公开 API 面。报告全文如下:

import { EnvironmentProviders } from '@angular/core';

// @public @deprecated
export function provideAnimationsAsync(type?: 'animations' | 'noop'): EnvironmentProviders;

// (No @packageDocumentation comment for this package)

这份报告传达了三个关键事实:

  1. 该包只有一个公开导出:函数 provideAnimationsAsync,没有类、接口或其他类型导出;
  2. 该 API 已被标记 @deprecated@public @deprecated 双注解),但仍属于 @public 层级,即当前版本仍可正常导入使用;
  3. 依赖面极小:仅从 @angular/core 导入 EnvironmentProviders,说明它是一个纯提供者工厂函数,不依赖任何具体渲染模块。

该包的定位由 PACKAGE.md 一句话概括:"Provides a lazy loaded infrastructure for the rendering of animations in supported browsers."(为受支持的浏览器提供动画渲染的懒加载基础设施)。其源码入口链路为:public_api.ts 重新导出 async-animations.ts,后者再导出 provideAnimationsAsync 与私有导出 private_export.ts

二、函数签名与参数说明

结合 API 报告与 providers.ts 源码,该函数的完整定义为:

export function provideAnimationsAsync(
  type: 'animations' | 'noop' = 'animations',
): EnvironmentProviders;

参数与返回值说明如下:

成员 类型 默认值 说明
type 'animations' | 'noop' 'animations' 传入 'noop' 时禁用动画(安装无操作动画渲染器);不传或传 'animations' 时启用真实动画
返回值 EnvironmentProviders 环境提供者集合,加入 bootstrapApplicationproviders 列表即可生效

源码中的 JSDoc 给出了标准用法示例(原样继承自 providers.ts 注释):

bootstrapApplication(RootComponent, {
  providers: [
    provideAnimationsAsync()
  ]
});

使用场景是:当应用通过 bootstrapApplication 函数启动(而非 NgModule 启动)时,无需再导入 BrowserAnimationsModule,只需将该函数返回的提供者加入 providers 列表。与同步版 provideAnimations() 的核心差异在于:异步版不会立即加载动画渲染器,动画模块会在首次真正需要时才动态导入,从而把 @angular/animations/browser 的体积移出关键渲染路径。

三、返回的提供者内部结构

provideAnimationsAsync 通过 makeEnvironmentProviders 返回两组环境提供者(见 providers.ts):

return makeEnvironmentProviders([
  {
    provide: RendererFactory2,
    useFactory: () => {
      return new AsyncAnimationRendererFactory(
        inject(DOCUMENT),
        inject(DomRendererFactory2),
        inject(NgZone),
        type,
      );
    },
  },
  {
    provide: ANIMATION_MODULE_TYPE,
    useValue: type === 'noop' ? 'NoopAnimations' : 'BrowserAnimations',
  },
]);
  • RendererFactory2 提供者:工厂函数会注入 DOCUMENTDomRendererFactory2(来自 index.ts 的私有符号 ɵDomRendererFactory2)、NgZonetype,构造 AsyncAnimationRendererFactory 实例。这个工厂是懒加载机制的载体;
  • ANIMATION_MODULE_TYPE 提供者:写入字符串值 'NoopAnimations''BrowserAnimations'@angular/commonNgIfNgClassNgStyle 等结构性指令会读取该 token 来决定是否安装动画钩子,因此它同时决定了"结构指令是否走动画路径";
  • 服务端自动降级:源码中存在显式判断——当 ngServerMode 为真时,type 会被强制改写为 'noop'("Animations don't work on the server so we switch them over to no-op automatically."),即在 SSR 环境中该函数自动退化为无动画模式,即使调用方传入了 'animations'
  • 函数入口还调用了 performanceMarkFeature('NgAsyncAnimations')(即 ɵperformanceMarkFeature),用于在性能 trace 中打点,标记应用启用了异步动画特性。

四、懒加载机制源码解析

懒加载的核心实现在 async_animation_renderer.ts 中的 AsyncAnimationRendererFactoryDynamicDelegationRenderer 两个类,它们通过私有导出 private_export.tsɵAsyncAnimationRendererFactory 等符号暴露给内部包。

4.1 模块动态导入与加载调度

loadImpl() 方法负责真正加载动画包:

private loadImpl(): Promise<AnimationRendererFactory> {
  const loadFn = () => this.moduleImpl ?? import('@angular/animations/browser').then((m) => m);

  let moduleImplPromise: typeof this.moduleImpl;
  if (this.loadingSchedulerFn) {
    moduleImplPromise = this.loadingSchedulerFn(loadFn);
  } else {
    moduleImplPromise = loadFn();
  }
  // ...
}

要点:

  • 使用原生 import('@angular/animations/browser') 动态导入,动画引擎与渲染器被拆分为独立 chunk;
  • moduleImpl 构造参数允许测试或内部场景注入一个 mock 模块(含 ɵcreateEngineɵAnimationRendererFactory),这就是测试中"提供 mock 实现"的入口;
  • 私有注入 token ɵASYNC_ANIMATION_LOADING_SCHEDULER_FN 允许替换加载调度函数,对 loadFn 进行二次封装(源码注释标明其为 "Private token for investigation purposes",即调研/实验用途);
  • 加载失败时抛出 RuntimeError(错误码 ANIMATION_RENDERER_ASYNC_LOADING_FAILURE),开发模式下提示:"Async loading for animations package was enabled, but loading failed. Angular falls back to using regular rendering. No animations will be displayed and their styles won't be applied."——即失败回退到普通 DOM 渲染,动画及其样式不再应用,但应用不会崩溃。

4.2 渲染器动态切换:DynamicDelegationRenderer

createRenderer() 是整个懒加载策略的枢纽(async_animation_renderer.ts):

  1. 先用当前委托(初始为 DomRendererFactory2)创建普通渲染器;
  2. 若渲染器类型已为 AnimationRendererType.Regular,说明动画工厂已加载,直接返回;
  3. 否则把该渲染器包装进 DynamicDelegationRenderer,并立即返回这个"动态代理渲染器";
  4. 仅当组件确实用到动画时才触发模块加载:判断条件是 rendererType?.data?.['animation'](即该组件声明了动画触发器),且尚未发起加载;
  5. 模块加载完成后,用新的 AnimationRendererFactory 再创建一个真正的动画渲染器,调用 dynamicRenderer.use(animationRenderer) 完成切换,并通过 ChangeDetectionScheduler.notify(NotificationSource.AsyncAnimationsLoaded) 通知调度器——这是触发一次额外变更检测的关键,因为渲染器切换后需要重放动画状态;
  6. 若加载失败,则 .catch 分支让代理永久使用普通渲染器

DynamicDelegationRendererɵtype = AnimationRendererType.Delegated)本身实现 Renderer2 接口,把每个调用透传给当前委托,但额外维护一个 replay 回放队列:

private shouldReplay(propOrEventName: string): boolean {
  //`null` indicates that we no longer need to collect events and properties
  return this.replay !== null && propOrEventName.startsWith(ANIMATION_PREFIX);
}

其中 ANIMATION_PREFIX = '@'。在动画模块加载完成之前,组件上设置的 @trigger 属性、@trigger.start/... 事件都会先透传给普通 DOM 渲染器(源码中还会把 throwOnSyntheticProps 置为 false,防止合成属性报错),同时被记录进 replay 队列;一旦 use() 切换到动画渲染器,队列中的 setProperty / listen 调用会被重放一遍,保证加载期间声明的动画状态在加载完成后依然完整生效。加载完成、队列处理完毕后 replay 被置为 null,停止收集。

另外,工厂实现了 OnDestroy:根视图移除时调用 this._engine?.flush(),源码注释解释了原因——异步动画模式下没有注入式引擎(InjectableAnimationEngine),TransitionAnimationEngine 只会 markElementAsRemoved(),必须 flush 才能真正移除 DOM 节点。

五、测试用例验证

animation_renderer_spec.ts 对上述机制做了直接验证,可作为行为依据:

  • 测试通过 provideAnimationsAsync() 提供动画能力(第 421、444、474 行等多处),并断言 factory.createRenderer(element, type) 返回的是 DynamicDelegationRenderer 实例(第 436 行 expect(renderer).toBeInstanceOf(DynamicDelegationRenderer));
  • 通过向 AsyncAnimationRendererFactory 注入 mock moduleImpl(含 ɵcreateEngine)控制加载时机,验证"加载前委托普通渲染器、加载后切换动画渲染器"的完整生命周期;
  • 私有导出 private_export.ts 暴露的 AsyncAnimationRendererFactoryDynamicDelegationRenderer 正是测试断言所需的符号。

六、弃用状态与迁移方向

API 报告中的 @deprecated 注解在源码中有明确出处(providers.ts 的 JSDoc):

@deprecated 20.2 Use animate.enter or animate.leave instead. Intent to remove in v23

即该函数自 v20.2 起被弃用,官方建议改用信号形式的 animate.enter / animate.leave 等 API,并计划在 v23 中移除。值得对照的是,同目录下的兄弟包 goldens/public-api/platform-browser/animations/index.api.md 中,同步版的 provideAnimations()provideNoopAnimations() 以及 BrowserAnimationsModuleNoopAnimationsModule 同样全部带有 @deprecated 标记——说明新旧两套动画提供方式正一同向声明式动画 API 迁移。对于新项目,建议直接采用文档推荐的最新动画 API;provideAnimationsAsync() 仍然适用于需要兼容既有启动方式、且要求动画包懒加载的存量应用(受支持浏览器、且非纯 SSR 场景,服务端会自动降级为 noop)。

七、关键文件索引

文件 作用
goldens/public-api/platform-browser/animations/async/index.api.md 本包公开 API 面的权威报告(本文核心依据)
packages/platform-browser/animations/async/src/providers.ts provideAnimationsAsync 实现与提供者结构
packages/platform-browser/animations/async/src/async_animation_renderer.ts 懒加载工厂与动态委托渲染器实现
packages/platform-browser/animations/async/src/async-animations.ts 公开导出入口
packages/platform-browser/animations/async/src/private_export.ts ɵ 私有符号导出
packages/platform-browser/animations/async/test/animation_renderer_spec.ts 渲染器切换行为测试
goldens/public-api/platform-browser/animations/index.api.md 同步版动画包 API 报告(对照迁移方向)
登录后查看全文
热门项目推荐
相关项目推荐