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
This commit is contained in:
Kristiyan Kostadinov 2024-12-20 11:53:29 +02:00 committed by Joey Perrott
parent c73aee21f3
commit dbb8980d03
2 changed files with 24 additions and 1 deletions

View file

@ -34,7 +34,8 @@ const ANIMATION_PREFIX = '@';
@Injectable()
export class AsyncAnimationRendererFactory implements OnDestroy, RendererFactory2 {
private _rendererFactoryPromise: Promise<AnimationRendererFactory> | 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) => {

View file

@ -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();
});
});
})();