From dbb8980d03485ad1cf0e19503c4e770b1ba0767e Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 20 Dec 2024 11:53:29 +0200 Subject: [PATCH] fix(platform-browser): avoid circular DI error in async renderer (#59271) In https://github.com/angular/components/pull/30179 the CDK overlay started depending on the `Renderer2Factory`. Since the overlay is used in the `MatSnackbar` which is commonly used in error handlers, `Overlay` can end up being injected as a part of the app initialization. Because `AsyncAnimationRendererFactory` depends on the `ChangeDetectionScheduler`, it may cause a circular dependency. These changes inject the `ChangeDetectionScheduler` lazily to avoid the error. Note: this will also be resolved by #58984, but I decided to send it out, because: 1. #58984 seems to be stuck on some internal cleanup. 2. The `AsyncAnimationRendererFactory` doesn't need the `scheduler` eagerly anyway so the change is fairly safe. Fixes #59255. PR Close #59271 --- .../async/src/async_animation_renderer.ts | 4 +++- .../async/test/animation_renderer_spec.ts | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/platform-browser/animations/async/src/async_animation_renderer.ts b/packages/platform-browser/animations/async/src/async_animation_renderer.ts index e9693eacece..e174badf5b8 100644 --- a/packages/platform-browser/animations/async/src/async_animation_renderer.ts +++ b/packages/platform-browser/animations/async/src/async_animation_renderer.ts @@ -34,7 +34,8 @@ const ANIMATION_PREFIX = '@'; @Injectable() export class AsyncAnimationRendererFactory implements OnDestroy, RendererFactory2 { private _rendererFactoryPromise: Promise | null = null; - private readonly scheduler = inject(ChangeDetectionScheduler, {optional: true}); + private scheduler: ChangeDetectionScheduler | null = null; + private readonly injector = inject(Injector); private readonly loadingSchedulerFn = inject(ɵASYNC_ANIMATION_LOADING_SCHEDULER_FN, { optional: true, }); @@ -143,6 +144,7 @@ export class AsyncAnimationRendererFactory implements OnDestroy, RendererFactory rendererType, ); dynamicRenderer.use(animationRenderer); + this.scheduler ??= this.injector.get(ChangeDetectionScheduler, null, {optional: true}); this.scheduler?.notify(NotificationSource.AsyncAnimationsLoaded); }) .catch((e) => { diff --git a/packages/platform-browser/animations/async/test/animation_renderer_spec.ts b/packages/platform-browser/animations/async/test/animation_renderer_spec.ts index 6caffb7f118..e6a766028f6 100644 --- a/packages/platform-browser/animations/async/test/animation_renderer_spec.ts +++ b/packages/platform-browser/animations/async/test/animation_renderer_spec.ts @@ -24,6 +24,7 @@ import { afterNextRender, ANIMATION_MODULE_TYPE, Component, + ErrorHandler, inject, Injectable, Injector, @@ -447,6 +448,26 @@ type AnimationBrowserModule = typeof import('@angular/animations/browser'); } }); }); + + it('should be able to inject the renderer factory in an ErrorHandler', async () => { + @Injectable({providedIn: 'root'}) + class CustomErrorHandler { + renderer = inject(RendererFactory2).createRenderer(null, null); + } + + @Component({template: ''}) + class App {} + + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + providers: [ + provideAnimationsAsync(), + {provide: ErrorHandler, useClass: CustomErrorHandler}, + ], + }); + + expect(() => TestBed.createComponent(App)).not.toThrow(); + }); }); })();