Skip to content

Commit 6aef0f6

Browse files
AleksanderBodurrithePunderWoman
authored andcommitted
fix(core): handle non-container environment injector cases (#52774)
Previously we had logic for a special case where a root injector in standalone apps would skip the import paths calculation step for the `getEnvironmentInjectorProviders` function. This commit intends to fix this for two other cases, namely: - When an injector is created by a route (via the `providers` field and lazy loading). - When an injector is manually created and attached to the injector tree It does this by assuming that any environment injector it cannot find a provider imports container for was created without one, and simply returns the raw provider records without the import paths calculation. PR Close #52774
1 parent f51ce68 commit 6aef0f6

File tree

2 files changed

+69
-14
lines changed

2 files changed

+69
-14
lines changed

packages/core/src/render3/util/injector_discovery_utils.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -405,16 +405,15 @@ function getEnvironmentInjectorProviders(injector: EnvironmentInjector): Provide
405405

406406
const providerImportsContainer = getProviderImportsContainer(injector);
407407
if (providerImportsContainer === null) {
408-
// There is a special case where the bootstrapped component does not
409-
// import any NgModules. In this case the environment injector connected to
410-
// that component is the root injector, which does not have a provider imports
411-
// container (and thus no concept of module import paths). Therefore we simply
412-
// return the provider records as is.
413-
if (isRootInjector(injector)) {
414-
return providerRecordsWithoutImportPaths;
415-
}
416-
417-
throwError('Could not determine where injector providers were configured.');
408+
// We assume that if an environment injector exists without an associated provider imports
409+
// container, it was created without such a container. Some examples cases where this could
410+
// happen:
411+
// - The root injector of a standalone application
412+
// - A router injector created by using the providers array in a lazy loaded route
413+
// - A manually created injector that is attached to the injector tree
414+
// Since each of these cases has no provider container, there is no concept of import paths,
415+
// so we can simply return the provider records.
416+
return providerRecordsWithoutImportPaths;
418417
}
419418

420419
const providerToPath = getProviderImportPaths(providerImportsContainer);
@@ -448,10 +447,6 @@ function isPlatformInjector(injector: Injector) {
448447
return injector instanceof R3Injector && injector.scopes.has('platform');
449448
}
450449

451-
function isRootInjector(injector: Injector) {
452-
return injector instanceof R3Injector && injector.scopes.has('root');
453-
}
454-
455450
/**
456451
* Gets the providers configured on an injector.
457452
*

packages/core/test/acceptance/injector_profiler_spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,66 @@ describe('getInjectorProviders', () => {
731731
expect(myServiceProviderRecord!.importPath![1]).toBe(ModuleA);
732732
}));
733733

734+
it('should be able to determine providers in a lazy route that has providers', fakeAsync(() => {
735+
class MyService {}
736+
737+
@Component({selector: 'my-comp-b', template: 'hello world', standalone: true})
738+
class MyStandaloneComponentB {
739+
injector = inject(Injector);
740+
}
741+
742+
@Component({
743+
selector: 'my-comp',
744+
template: `<router-outlet/>`,
745+
imports: [MyStandaloneComponentB, RouterOutlet],
746+
standalone: true
747+
})
748+
class MyStandaloneComponent {
749+
injector = inject(Injector);
750+
@ViewChild(RouterOutlet) routerOutlet: RouterOutlet|undefined;
751+
}
752+
753+
TestBed.configureTestingModule({
754+
imports: [RouterModule.forRoot([
755+
{
756+
path: 'lazy',
757+
loadComponent: () => MyStandaloneComponentB,
758+
providers: [MyService],
759+
},
760+
])]
761+
});
762+
const root = TestBed.createComponent(MyStandaloneComponent);
763+
TestBed.inject(Router).navigateByUrl('/lazy');
764+
tick();
765+
root.detectChanges();
766+
767+
const myStandalonecomponentB =
768+
root.componentRef.instance!.routerOutlet!.component as MyStandaloneComponentB;
769+
const routeEnvironmentInjector =
770+
(myStandalonecomponentB.injector.get(EnvironmentInjector) as R3Injector);
771+
expect(routeEnvironmentInjector).toBeTruthy();
772+
expect(routeEnvironmentInjector.source).toBeTruthy();
773+
expect(routeEnvironmentInjector.source!.startsWith('Route:')).toBeTrue();
774+
775+
const myComponentBEnvironmentInjectorProviders =
776+
getInjectorProviders(routeEnvironmentInjector);
777+
const myServiceProviderRecord =
778+
myComponentBEnvironmentInjectorProviders.find(provider => provider.token === MyService);
779+
expect(myServiceProviderRecord).toBeTruthy();
780+
expect(myServiceProviderRecord!.provider).toBe(MyService);
781+
expect(myServiceProviderRecord!.token).toBe(MyService);
782+
}));
783+
784+
it('should be able to determine providers in an injector that was created manually',
785+
fakeAsync(() => {
786+
class MyService {}
787+
const injector = Injector.create({providers: [MyService]}) as EnvironmentInjector;
788+
const providers = getInjectorProviders(injector);
789+
expect(providers.length).toBe(1);
790+
expect(providers[0].token).toBe(MyService);
791+
expect(providers[0].provider).toBe(MyService);
792+
}));
793+
734794
it('should be able to get injector providers for element injectors created by components rendering in an ngFor',
735795
() => {
736796
class MyService {}

0 commit comments

Comments
 (0)