Skip to content

Commit 54ea3b6

Browse files
AleksanderBodurridylhunn
authored andcommitted
fix(core): emit provider configured event when a service is configured with providedIn (#52365)
Previously this case was missed by the default framework injector profiler. Now in ngDevMode this event emits correctly when a service is configured with `providedIn`. This includes the case where injection tokens are configured with a `providedIn`. This commit also includes unit tests for this new case in the injector profiler. PR Close #52365
1 parent 372dd0a commit 54ea3b6

File tree

3 files changed

+113
-9
lines changed

3 files changed

+113
-9
lines changed

packages/core/src/di/r3_injector.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {catchInjectorError, convertToBitFlags, injectArgs, NG_TEMP_TOKEN_PATH, s
2828
import {INJECTOR} from './injector_token';
2929
import {getInheritedInjectableDef, getInjectableDef, InjectorType, ɵɵInjectableDeclaration} from './interface/defs';
3030
import {InjectFlags, InjectOptions} from './interface/injector';
31-
import {ClassProvider, ConstructorProvider, EnvironmentProviders, InternalEnvironmentProviders, isEnvironmentProviders, Provider, StaticClassProvider} from './interface/provider';
31+
import {ClassProvider, ConstructorProvider, EnvironmentProviders, InternalEnvironmentProviders, isEnvironmentProviders, Provider, StaticClassProvider, TypeProvider} from './interface/provider';
3232
import {INJECTOR_DEF_TYPES} from './internal_tokens';
3333
import {NullInjector} from './null_injector';
3434
import {isExistingProvider, isFactoryProvider, isTypeProvider, isValueProvider, SingleProvider} from './provider_collection';
@@ -271,6 +271,13 @@ export class R3Injector extends EnvironmentInjector {
271271
if (def && this.injectableDefInScope(def)) {
272272
// Found an injectable def and it's scoped to this injector. Pretend as if it was here
273273
// all along.
274+
275+
if (ngDevMode) {
276+
runInInjectorProfilerContext(this, token as Type<T>, () => {
277+
emitProviderConfiguredEvent(token as TypeProvider);
278+
});
279+
}
280+
274281
record = makeRecord(injectableDefOrInjectorDefFactory(token), NOT_YET);
275282
} else {
276283
record = null;

packages/core/src/render3/debug/injector_profiler.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import type {FactoryProvider} from '../../di';
910
import {resolveForwardRef} from '../../di/forward_ref';
1011
import {InjectionToken} from '../../di/injection_token';
1112
import type {Injector} from '../../di/injector';
@@ -86,7 +87,7 @@ export interface ProviderRecord {
8687
/**
8788
* DI token that this provider is configuring
8889
*/
89-
token: Type<unknown>;
90+
token: Type<unknown>|InjectionToken<unknown>;
9091

9192
/**
9293
* Determines if provider is configured as view provider.
@@ -192,20 +193,39 @@ function injectorProfiler(event: InjectorProfilerEvent): void {
192193
* Emits an InjectorProfilerEventType.ProviderConfigured to the injector profiler. The data in the
193194
* emitted event includes the raw provider, as well as the token that provider is providing.
194195
*
195-
* @param provider A provider object
196+
* @param eventProvider A provider object
196197
*/
197198
export function emitProviderConfiguredEvent(
198-
provider: SingleProvider, isViewProvider: boolean = false): void {
199+
eventProvider: SingleProvider, isViewProvider: boolean = false): void {
199200
!ngDevMode && throwError('Injector profiler should never be called in production mode');
200201

202+
let token;
203+
// if the provider is a TypeProvider (typeof provider is function) then the token is the
204+
// provider itself
205+
if (typeof eventProvider === 'function') {
206+
token = eventProvider;
207+
}
208+
// if the provider is an injection token, then the token is the injection token.
209+
else if (eventProvider instanceof InjectionToken) {
210+
token = eventProvider;
211+
}
212+
// in all other cases we can access the token via the `provide` property of the provider
213+
else {
214+
token = resolveForwardRef(eventProvider.provide);
215+
}
216+
217+
let provider = eventProvider;
218+
// Injection tokens may define their own default provider which gets attached to the token itself
219+
// as `ɵprov`. In this case, we want to emit the provider that is attached to the token, not the
220+
// token itself.
221+
if (eventProvider instanceof InjectionToken) {
222+
provider = eventProvider.ɵprov as FactoryProvider || eventProvider;
223+
}
224+
201225
injectorProfiler({
202226
type: InjectorProfilerEventType.ProviderConfigured,
203227
context: getInjectorProfilerContext(),
204-
providerRecord: {
205-
token: typeof provider === 'function' ? provider : resolveForwardRef(provider.provide),
206-
provider,
207-
isViewProvider
208-
}
228+
providerRecord: {token, provider, isViewProvider}
209229
});
210230
}
211231

packages/core/test/acceptance/injector_profiler_spec.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,83 @@ describe('setProfiler', () => {
209209
expect(((myServiceProviderConfiguredEvent!.providerRecord)?.provider as ClassProvider).multi)
210210
.toBeTrue();
211211
});
212+
213+
it('should emit correct DI events when service providers are configured with providedIn', () => {
214+
@Injectable({providedIn: 'root'})
215+
class RootService {
216+
}
217+
218+
@Injectable({providedIn: 'platform'})
219+
class PlatformService {
220+
}
221+
222+
const providedInRootInjectionToken = new InjectionToken(
223+
'providedInRootInjectionToken', {providedIn: 'root', factory: () => 'hello world'});
224+
225+
const providedInPlatformToken = new InjectionToken(
226+
'providedInPlatformToken', {providedIn: 'platform', factory: () => 'hello world'});
227+
228+
@Component({
229+
selector: 'my-comp',
230+
template: 'hello world',
231+
})
232+
class MyComponent {
233+
rootService = inject(RootService);
234+
platformService = inject(PlatformService);
235+
fromRoot = inject(providedInRootInjectionToken);
236+
fromPlatform = inject(providedInPlatformToken);
237+
}
238+
239+
TestBed.configureTestingModule({declarations: [MyComponent]});
240+
TestBed.createComponent(MyComponent);
241+
242+
// MyService should have been configured
243+
const rootServiceProviderConfiguredEvent = searchForProfilerEvent<ProviderConfiguredEvent>(
244+
providerConfiguredEvents, (event) => event.providerRecord.token === RootService);
245+
246+
expect(rootServiceProviderConfiguredEvent).toBeTruthy();
247+
expect(rootServiceProviderConfiguredEvent!.context).toBeTruthy();
248+
expect(rootServiceProviderConfiguredEvent!.context!.injector).toBeInstanceOf(R3Injector);
249+
expect((rootServiceProviderConfiguredEvent!.context!.injector as R3Injector).scopes.has('root'))
250+
.toBeTrue();
251+
252+
const platformServiceProviderConfiguredEvent = searchForProfilerEvent<ProviderConfiguredEvent>(
253+
providerConfiguredEvents, (event) => event.providerRecord.token === PlatformService);
254+
expect(platformServiceProviderConfiguredEvent).toBeTruthy();
255+
expect(platformServiceProviderConfiguredEvent!.context).toBeTruthy();
256+
expect(platformServiceProviderConfiguredEvent!.context!.injector).toBeInstanceOf(R3Injector);
257+
expect((platformServiceProviderConfiguredEvent!.context!.injector as R3Injector)
258+
.scopes.has('platform'))
259+
.toBeTrue();
260+
261+
const providedInRootInjectionTokenProviderConfiguredEvent =
262+
searchForProfilerEvent<ProviderConfiguredEvent>(
263+
providerConfiguredEvents,
264+
(event) => event.providerRecord.token === providedInRootInjectionToken);
265+
expect(providedInRootInjectionTokenProviderConfiguredEvent).toBeTruthy();
266+
expect(providedInRootInjectionTokenProviderConfiguredEvent!.context).toBeTruthy();
267+
expect(providedInRootInjectionTokenProviderConfiguredEvent!.context!.injector)
268+
.toBeInstanceOf(R3Injector);
269+
expect((providedInRootInjectionTokenProviderConfiguredEvent!.context!.injector as R3Injector)
270+
.scopes.has('root'))
271+
.toBeTrue();
272+
expect(providedInRootInjectionTokenProviderConfiguredEvent!.providerRecord.token)
273+
.toBe(providedInRootInjectionToken);
274+
275+
const providedInPlatformTokenProviderConfiguredEvent =
276+
searchForProfilerEvent<ProviderConfiguredEvent>(
277+
providerConfiguredEvents,
278+
(event) => event.providerRecord.token === providedInPlatformToken);
279+
expect(providedInPlatformTokenProviderConfiguredEvent).toBeTruthy();
280+
expect(providedInPlatformTokenProviderConfiguredEvent!.context).toBeTruthy();
281+
expect(providedInPlatformTokenProviderConfiguredEvent!.context!.injector)
282+
.toBeInstanceOf(R3Injector);
283+
expect((providedInPlatformTokenProviderConfiguredEvent!.context!.injector as R3Injector)
284+
.scopes.has('platform'))
285+
.toBeTrue();
286+
expect(providedInPlatformTokenProviderConfiguredEvent!.providerRecord.token)
287+
.toBe(providedInPlatformToken);
288+
});
212289
});
213290

214291
describe('getInjectorProviders', () => {

0 commit comments

Comments
 (0)