mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
fix(core): use TNode instead of LView for mapping injector providers (#52436)
Previously, LViews were used here to be consistent with other debug APIs. Using LViews for tracking injector providers does not work because providers only get configured once per TNode type. Now we use the TNode as the key to track element injector providers, allowing the injector for each item rendered in a list (`ngFor` or `@for`) to be targeted with debug APIs for inspecting providers PR Close #52436
This commit is contained in:
parent
435dd32912
commit
3d73b0cbfb
3 changed files with 104 additions and 9 deletions
|
|
@ -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<Injector|LView, WeakMap<Type<unknown>, InjectedService[]>>();
|
||||
resolverToProviders = new WeakMap<Injector|LView, ProviderRecord[]>();
|
||||
resolverToProviders = new WeakMap<Injector|TNode, ProviderRecord[]>();
|
||||
standaloneInjectorToComponent = new WeakMap<Injector, Type<unknown>>();
|
||||
|
||||
reset() {
|
||||
this.resolverToTokenToDependencies =
|
||||
new WeakMap<Injector|LView, WeakMap<Type<unknown>, InjectedService[]>>();
|
||||
this.resolverToProviders = new WeakMap<Injector|LView, ProviderRecord[]>();
|
||||
this.resolverToProviders = new WeakMap<Injector|TNode, ProviderRecord[]>();
|
||||
this.standaloneInjectorToComponent = new WeakMap<Injector, Type<unknown>>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,9 +205,9 @@ function getProviderImportsContainer(injector: Injector): Type<unknown>|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) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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: `
|
||||
<item-cmp *ngFor="let item of items"></item-cmp>
|
||||
`,
|
||||
imports: [ItemComponent, NgForOf],
|
||||
standalone: true
|
||||
})
|
||||
class MyStandaloneComponent {
|
||||
injector = inject(Injector);
|
||||
items = [1, 2, 3];
|
||||
|
||||
@ViewChildren(ItemComponent) itemComponents: QueryList<ItemComponent>|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) {
|
||||
<item-cmp></item-cmp>
|
||||
}
|
||||
`,
|
||||
imports: [ItemComponent],
|
||||
standalone: true
|
||||
})
|
||||
class MyStandaloneComponent {
|
||||
injector = inject(Injector);
|
||||
items = [1, 2, 3];
|
||||
|
||||
@ViewChildren(ItemComponent) itemComponents: QueryList<ItemComponent>|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', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue