Skip to content

Commit 31b8870

Browse files
AleksanderBodurridylhunn
authored andcommitted
fix(core): emit provider configured event when a service is configured with providedIn (angular#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 angular#52365
1 parent 4726203 commit 31b8870

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';
@@ -270,6 +270,13 @@ export class R3Injector extends EnvironmentInjector {
270270
if (def && this.injectableDefInScope(def)) {
271271
// Found an injectable def and it's scoped to this injector. Pretend as if it was here
272272
// all along.
273+
274+
if (ngDevMode) {
275+
runInInjectorProfilerContext(this, token as Type<T>, () => {
276+
emitProviderConfiguredEvent(token as TypeProvider);
277+
});
278+
}
279+
273280
record = makeRecord(injectableDefOrInjectorDefFactory(token), NOT_YET);
274281
} else {
275282
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';
@@ -88,7 +89,7 @@ export interface ProviderRecord {
8889
/**
8990
* DI token that this provider is configuring
9091
*/
91-
token: Type<unknown>;
92+
token: Type<unknown>|InjectionToken<unknown>;
9293

9394
/**
9495
* Determines if provider is configured as view provider.
@@ -199,20 +200,39 @@ function injectorProfiler(event: InjectorProfilerEvent): void {
199200
* Emits an InjectorProfilerEventType.ProviderConfigured to the injector profiler. The data in the
200201
* emitted event includes the raw provider, as well as the token that provider is providing.
201202
*
202-
* @param provider A provider object
203+
* @param eventProvider A provider object
203204
*/
204205
export function emitProviderConfiguredEvent(
205-
provider: SingleProvider, isViewProvider: boolean = false): void {
206+
eventProvider: SingleProvider, isViewProvider: boolean = false): void {
206207
!ngDevMode && throwError('Injector profiler should never be called in production mode');
207208

209+
let token;
210+
// if the provider is a TypeProvider (typeof provider is function) then the token is the
211+
// provider itself
212+
if (typeof eventProvider === 'function') {
213+
token = eventProvider;
214+
}
215+
// if the provider is an injection token, then the token is the injection token.
216+
else if (eventProvider instanceof InjectionToken) {
217+
token = eventProvider;
218+
}
219+
// in all other cases we can access the token via the `provide` property of the provider
220+
else {
221+
token = resolveForwardRef(eventProvider.provide);
222+
}
223+
224+
let provider = eventProvider;
225+
// Injection tokens may define their own default provider which gets attached to the token itself
226+
// as `ɵprov`. In this case, we want to emit the provider that is attached to the token, not the
227+
// token itself.
228+
if (eventProvider instanceof InjectionToken) {
229+
provider = eventProvider.ɵprov as FactoryProvider || eventProvider;
230+
}
231+
208232
injectorProfiler({
209233
type: InjectorProfilerEventType.ProviderConfigured,
210234
context: getInjectorProfilerContext(),
211-
providerRecord: {
212-
token: typeof provider === 'function' ? provider : resolveForwardRef(provider.provide),
213-
provider,
214-
isViewProvider
215-
}
235+
providerRecord: {token, provider, isViewProvider}
216236
});
217237
}
218238

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('getInjectorMetadata', () => {

0 commit comments

Comments
 (0)