Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/core/src/render3/util/global_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {setProfiler} from '../profiler';

import {applyChanges} from './change_detection_utils';
import {getComponent, getContext, getDirectiveMetadata, getDirectives, getHostElement, getInjector, getListeners, getOwningComponent, getRootComponents} from './discovery_utils';
import {getDependenciesFromInjectable, getInjectorProviders, getInjectorResolutionPath} from './injector_discovery_utils';
import {getDependenciesFromInjectable, getInjectorMetadata, getInjectorProviders, getInjectorResolutionPath} from './injector_discovery_utils';



Expand Down Expand Up @@ -48,6 +48,7 @@ export function publishDefaultGlobalUtils() {
publishGlobalUtil('ɵgetDependenciesFromInjectable', getDependenciesFromInjectable);
publishGlobalUtil('ɵgetInjectorProviders', getInjectorProviders);
publishGlobalUtil('ɵgetInjectorResolutionPath', getInjectorResolutionPath);
publishGlobalUtil('ɵgetInjectorMetadata', getInjectorMetadata);
/**
* Warning: this function is *INTERNAL* and should not be relied upon in application's code.
* The contract of the function might be changed in any release and/or the function can be
Expand Down
43 changes: 41 additions & 2 deletions packages/core/src/render3/util/injector_discovery_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,19 @@ import {EnvironmentInjector, R3Injector} from '../../di/r3_injector';
import {Type} from '../../interface/type';
import {NgModuleRef as viewEngine_NgModuleRef} from '../../linker/ng_module_factory';
import {deepForEach} from '../../util/array_utils';
import {throwError} from '../../util/assert';
import {assertDefined, throwError} from '../../util/assert';
import type {ChainedInjector} from '../component_ref';
import {getComponentDef} from '../definition';
import {getNodeInjectorLView, getNodeInjectorTNode, getParentInjectorLocation, NodeInjector} from '../di';
import {getFrameworkDIDebugData} from '../debug/framework_injector_profiler';
import {InjectedService, ProviderRecord} from '../debug/injector_profiler';
import {NodeInjectorOffset} from '../interfaces/injector';
import {TContainerNode, TElementContainerNode, TElementNode, TNode} from '../interfaces/node';
import {INJECTOR, LView, TVIEW} from '../interfaces/view';
import {HOST, INJECTOR, LView, TVIEW} from '../interfaces/view';

import {getParentInjectorIndex, getParentInjectorView, hasParentInjector} from './injector_utils';
import {assertTNodeForLView} from '../assert';
import {RElement} from '../interfaces/renderer_dom';

/**
* Discovers the dependencies of an injectable instance. Provides DI information about each
Expand Down Expand Up @@ -390,6 +392,43 @@ export function getInjectorProviders(injector: Injector): ProviderRecord[] {
throwError('getInjectorProviders only supports NodeInjector and EnvironmentInjector');
}

/**
*
* Given an injector, this function will return
* an object containing the type and source of the injector.
*
* | | type | source |
* |--------------|-------------|-------------------------------------------------------------|
* | NodeInjector | element | DOM element that created this injector |
* | R3Injector | environment | `injector.source` |
* | NullInjector | null | null |
*
* @param injector the Injector to get metadata for
* @returns an object containing the type and source of the given injector. If the injector metadata
* cannot be determined, returns null.
*/
export function getInjectorMetadata(injector: Injector):
{type: string; source: RElement | string | null}|null {
if (injector instanceof NodeInjector) {
const lView = getNodeInjectorLView(injector);
const tNode = getNodeInjectorTNode(injector)!;
assertTNodeForLView(tNode, lView);
assertDefined(lView[tNode.index][HOST], 'Could not find node in element view.');

return {type: 'element', source: lView[tNode.index][HOST]};
}

if (injector instanceof R3Injector) {
return {type: 'environment', source: injector.source ?? null};
}

if (injector instanceof NullInjector) {
return {type: 'null', source: null};
}

return null;
}

export function getInjectorResolutionPath(injector: Injector): Injector[] {
const resolutionPath: Injector[] = [injector];
getInjectorResolutionPathHelper(injector, resolutionPath);
Expand Down
114 changes: 111 additions & 3 deletions packages/core/test/acceptance/injector_profiler_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@

import {PercentPipe} from '@angular/common';
import {inject} from '@angular/core';
import {ClassProvider, Component, Directive, Inject, Injectable, InjectFlags, InjectionToken, Injector, NgModule, NgModuleRef, ViewChild} from '@angular/core/src/core';
import {afterRender, ClassProvider, Component, Directive, ElementRef, Injectable, InjectFlags, InjectionToken, InjectOptions, Injector, NgModule, NgModuleRef, ProviderToken, ViewChild} 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';
import {setupFrameworkInjectorProfiler} from '@angular/core/src/render3/debug/framework_injector_profiler';
import {getInjectorProfilerContext, InjectedService, InjectedServiceEvent, InjectorCreatedInstanceEvent, InjectorProfilerEvent, InjectorProfilerEventType, ProviderConfiguredEvent, ProviderRecord, setInjectorProfiler} from '@angular/core/src/render3/debug/injector_profiler';
import {getInjectorProfilerContext, InjectedServiceEvent, InjectorCreatedInstanceEvent, InjectorProfilerEvent, InjectorProfilerEventType, ProviderConfiguredEvent, setInjectorProfiler} from '@angular/core/src/render3/debug/injector_profiler';
import {getNodeInjectorLView, NodeInjector} from '@angular/core/src/render3/di';
import {getDependenciesFromInjectable, getInjectorProviders, getInjectorResolutionPath} from '@angular/core/src/render3/util/injector_discovery_utils';
import {getDependenciesFromInjectable, getInjectorMetadata, getInjectorProviders, getInjectorResolutionPath} from '@angular/core/src/render3/util/injector_discovery_utils';
import {fakeAsync, tick} from '@angular/core/testing';
import {TestBed} from '@angular/core/testing/src/test_bed';
import {BrowserModule} from '@angular/platform-browser';
Expand Down Expand Up @@ -211,6 +211,114 @@ describe('setProfiler', () => {
});
});

describe('getInjectorMetadata', () => {
it('should be able to determine injector type and name', fakeAsync(() => {
class MyServiceA {}
@NgModule({providers: [MyServiceA]})
class ModuleA {
}

class MyServiceB {}
@NgModule({providers: [MyServiceB]})
class ModuleB {
}

@Component({
selector: 'lazy-comp',
template: `lazy component`,
standalone: true,
imports: [ModuleB]
})
class LazyComponent {
lazyComponentNodeInjector = inject(Injector);
elementRef = inject(ElementRef);

constructor() {
afterRender(() => afterLazyComponentRendered(this));
}
}

@Component({
standalone: true,
imports: [RouterOutlet, ModuleA],
template: `<router-outlet/>`,
})
class MyStandaloneComponent {
@ViewChild(RouterOutlet, {read: ElementRef}) routerOutlet: ElementRef|undefined;
elementRef = inject(ElementRef);
}

TestBed.configureTestingModule({
imports: [RouterModule.forRoot([{
path: 'lazy',
loadComponent: () => LazyComponent,
}])]
});

const root = TestBed.createComponent(MyStandaloneComponent);
TestBed.inject(Router).navigateByUrl('/lazy');
tick();
root.detectChanges();

function afterLazyComponentRendered(lazyComponent: LazyComponent) {
const {lazyComponentNodeInjector} = lazyComponent;
const myStandaloneComponent =
lazyComponentNodeInjector.get(MyStandaloneComponent, null, {skipSelf: true})!;
expect(myStandaloneComponent).toBeInstanceOf(MyStandaloneComponent);
expect(myStandaloneComponent.routerOutlet).toBeInstanceOf(ElementRef);

const injectorPath = getInjectorResolutionPath(lazyComponentNodeInjector);
const injectorMetadata = injectorPath.map(injector => getInjectorMetadata(injector));

expect(injectorMetadata[0]).toBeDefined();
expect(injectorMetadata[1]).toBeDefined();
expect(injectorMetadata[2]).toBeDefined();
expect(injectorMetadata[3]).toBeDefined();
expect(injectorMetadata[4]).toBeDefined();
expect(injectorMetadata[5]).toBeDefined();
expect(injectorMetadata[6]).toBeDefined();
expect(injectorMetadata[7]).toBeDefined();

expect(injectorMetadata[0]!.source).toBe(lazyComponent.elementRef.nativeElement);
expect(injectorMetadata[1]!.source)
.toBe(myStandaloneComponent.routerOutlet!.nativeElement);
expect(injectorMetadata[2]!.source).toBe(myStandaloneComponent.elementRef.nativeElement);
expect(injectorMetadata[3]!.source).toBe('Standalone[LazyComponent]');
expect(injectorMetadata[4]!.source).toBe('Standalone[MyStandaloneComponent]');
expect(injectorMetadata[5]!.source).toBe('DynamicTestModule');
expect(injectorMetadata[6]!.source).toBe('Platform: core');
expect(injectorMetadata[7]!.source).toBeNull();

expect(injectorMetadata[0]!.type).toBe('element');
expect(injectorMetadata[1]!.type).toBe('element');
expect(injectorMetadata[2]!.type).toBe('element');
expect(injectorMetadata[3]!.type).toBe('environment');
expect(injectorMetadata[4]!.type).toBe('environment');
expect(injectorMetadata[5]!.type).toBe('environment');
expect(injectorMetadata[6]!.type).toBe('environment');
expect(injectorMetadata[7]!.type).toBe('null');
}
}));

it('should return null for injectors it does not recognize', () => {
class MockInjector extends Injector {
override get(): void {
throw new Error('Method not implemented.');
}
}
const mockInjector = new MockInjector();
expect(getInjectorMetadata(mockInjector)).toBeNull();
});

it('should return null as the source for an R3Injector with no source.', () => {
const emptyR3Injector = new R3Injector([], new NullInjector(), null, new Set());
const r3InjectorMetadata = getInjectorMetadata(emptyR3Injector);
expect(r3InjectorMetadata).toBeDefined();
expect(r3InjectorMetadata!.source).toBeNull();
expect(r3InjectorMetadata!.type).toBe('environment');
});
});

describe('getInjectorProviders', () => {
beforeEach(() => setupFrameworkInjectorProfiler());
afterAll(() => setInjectorProfiler(null));
Expand Down