diff --git a/packages/core/src/render3/debug/framework_injector_profiler.ts b/packages/core/src/render3/debug/framework_injector_profiler.ts index 43f1ef86e8d..2184ecc548f 100644 --- a/packages/core/src/render3/debug/framework_injector_profiler.ts +++ b/packages/core/src/render3/debug/framework_injector_profiler.ts @@ -27,9 +27,12 @@ import {InjectedService, InjectorCreatedInstance, InjectorProfilerContext, Injec * getDependenciesFromInjectable API, which takes in an injector and a token and returns it's * dependencies. * - * resolverToProviders: Maps a DI resolver (an Injector or an LView) to the providers configured + * resolverToProviders: Maps a DI resolver (an Injector or a TNode) to the providers configured * within it This is used to support the getInjectorProviders API, which takes in an injector and - * returns the providers that it was configured with. + * returns the providers that it was configured with. Note that for the element injector case we + * use the TNode instead of the LView as the DI resolver. This is because the registration of + * providers happens only once per type of TNode. If an injector is created with an identical TNode, + * the providers for that injector will not be reconfigured. * * standaloneInjectorToComponent: Maps the injector of a standalone component to the standalone * component that it is associated with. Used in the getInjectorProviders API, specificially in the @@ -53,13 +56,13 @@ import {InjectedService, InjectorCreatedInstance, InjectorProfilerContext, Injec class DIDebugData { resolverToTokenToDependencies = new WeakMap, InjectedService[]>>(); - resolverToProviders = new WeakMap(); + resolverToProviders = new WeakMap(); standaloneInjectorToComponent = new WeakMap>(); reset() { this.resolverToTokenToDependencies = new WeakMap, InjectedService[]>>(); - this.resolverToProviders = new WeakMap(); + this.resolverToProviders = new WeakMap(); this.standaloneInjectorToComponent = new WeakMap>(); } } @@ -237,7 +240,13 @@ function handleProviderConfiguredEvent( context: InjectorProfilerContext, data: ProviderRecord): void { const {resolverToProviders} = frameworkDIDebugData; - const diResolver = getDIResolver(context?.injector); + let diResolver: Injector|TNode; + if (context?.injector instanceof NodeInjector) { + diResolver = getNodeInjectorTNode(context.injector) as TNode; + } else { + diResolver = context.injector; + } + if (diResolver === null) { throwError('A ProviderConfigured event must be run within an injection context.'); } diff --git a/packages/core/src/render3/util/injector_discovery_utils.ts b/packages/core/src/render3/util/injector_discovery_utils.ts index 327de2c2b5b..6cb85c4758f 100644 --- a/packages/core/src/render3/util/injector_discovery_utils.ts +++ b/packages/core/src/render3/util/injector_discovery_utils.ts @@ -205,9 +205,9 @@ function getProviderImportsContainer(injector: Injector): Type|null { * injector */ function getNodeInjectorProviders(injector: NodeInjector): ProviderRecord[] { - const diResolver = getNodeInjectorLView(injector); + const diResolver = getNodeInjectorTNode(injector); const {resolverToProviders} = getFrameworkDIDebugData(); - return resolverToProviders.get(diResolver) ?? []; + return resolverToProviders.get(diResolver as TNode) ?? []; } /** diff --git a/packages/core/test/acceptance/injector_profiler_spec.ts b/packages/core/test/acceptance/injector_profiler_spec.ts index c90c239bdd5..d8595d5eb62 100644 --- a/packages/core/test/acceptance/injector_profiler_spec.ts +++ b/packages/core/test/acceptance/injector_profiler_spec.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {PercentPipe} from '@angular/common'; +import {NgForOf, PercentPipe} from '@angular/common'; import {inject} from '@angular/core'; -import {afterRender, ClassProvider, Component, Directive, ElementRef, Injectable, InjectFlags, InjectionToken, InjectOptions, Injector, NgModule, NgModuleRef, ProviderToken, ViewChild} from '@angular/core/src/core'; +import {afterRender, ClassProvider, Component, Directive, ElementRef, Injectable, InjectFlags, InjectionToken, Injector, NgModule, NgModuleRef, QueryList, ViewChild, ViewChildren} from '@angular/core/src/core'; import {NullInjector} from '@angular/core/src/di/null_injector'; import {isClassProvider, isExistingProvider, isFactoryProvider, isTypeProvider, isValueProvider} from '@angular/core/src/di/provider_collection'; import {EnvironmentInjector, R3Injector} from '@angular/core/src/di/r3_injector'; @@ -730,6 +730,92 @@ describe('getInjectorProviders', () => { expect(myServiceProviderRecord!.importPath![0]).toBe(MyStandaloneComponentB); expect(myServiceProviderRecord!.importPath![1]).toBe(ModuleA); })); + + it('should be able to get injector providers for element injectors created by components rendering in an ngFor', + () => { + class MyService {} + + @Component( + {selector: 'item-cmp', template: 'item', standalone: true, providers: [MyService]}) + class ItemComponent { + injector = inject(Injector); + } + + @Component({ + selector: 'my-comp', + template: ` + + `, + imports: [ItemComponent, NgForOf], + standalone: true + }) + class MyStandaloneComponent { + injector = inject(Injector); + items = [1, 2, 3]; + + @ViewChildren(ItemComponent) itemComponents: QueryList|undefined; + } + + const root = TestBed.createComponent(MyStandaloneComponent); + root.detectChanges(); + + const myStandaloneComponent = root.componentRef.instance; + const itemComponents = myStandaloneComponent.itemComponents; + expect(itemComponents).toBeInstanceOf(QueryList); + expect(itemComponents?.length).toBe(3); + itemComponents!.forEach(item => { + const itemProviders = getInjectorProviders(item.injector); + expect(itemProviders).toBeInstanceOf(Array); + expect(itemProviders.length).toBe(1); + expect(itemProviders[0].token).toBe(MyService); + expect(itemProviders[0].provider).toBe(MyService); + expect(itemProviders[0].isViewProvider).toBe(false); + }); + }); + + it('should be able to get injector providers for element injectors created by components rendering in a @for', + () => { + class MyService {} + + @Component( + {selector: 'item-cmp', template: 'item', standalone: true, providers: [MyService]}) + class ItemComponent { + injector = inject(Injector); + } + + @Component({ + selector: 'my-comp', + template: ` + @for (item of items; track item) { + + } + `, + imports: [ItemComponent], + standalone: true + }) + class MyStandaloneComponent { + injector = inject(Injector); + items = [1, 2, 3]; + + @ViewChildren(ItemComponent) itemComponents: QueryList|undefined; + } + + const root = TestBed.createComponent(MyStandaloneComponent); + root.detectChanges(); + + const myStandaloneComponent = root.componentRef.instance; + const itemComponents = myStandaloneComponent.itemComponents; + expect(itemComponents).toBeInstanceOf(QueryList); + expect(itemComponents?.length).toBe(3); + itemComponents!.forEach(item => { + const itemProviders = getInjectorProviders(item.injector); + expect(itemProviders).toBeInstanceOf(Array); + expect(itemProviders.length).toBe(1); + expect(itemProviders[0].token).toBe(MyService); + expect(itemProviders[0].provider).toBe(MyService); + expect(itemProviders[0].isViewProvider).toBe(false); + }); + }); }); describe('getDependenciesFromInjectable', () => {