Skip to content

Commit

Permalink
refactor(core): internally support providedIn: env
Browse files Browse the repository at this point in the history
This commit adds a new internal scope to `R3Injector` for `EnvInjector`s
specifically. This will allow us to scope services to the environment side
of the injector hierarchy specifically, as opposed to the `'any'` scope
which also includes view-side injectors created via `Injector.create`. For
now, this functionality is not exposed publicly, but is available to use
within `@angular/core` only.
  • Loading branch information
alxhub committed Apr 14, 2022
1 parent f9a696c commit ee45cb9
Show file tree
Hide file tree
Showing 7 changed files with 36 additions and 21 deletions.
Expand Up @@ -79,6 +79,9 @@ export class DirectiveDecoratorHandler implements
// been processed, but we want to enforce a consistent decorator mental model.
// See: https://v9.angular.io/guide/migration-undecorated-classes.
if (this.compileUndecoratedClassesWithAngularFeatures === false && decorator === null) {
if (this.isCore) {
return {};
}
return {diagnostics: [getUndecoratedClassWithAngularFeaturesDiagnostic(node)]};
}

Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/di/interface/defs.ts
Expand Up @@ -8,6 +8,7 @@

import {Type} from '../../interface/type';
import {getClosureSafeProperty} from '../../util/property';

import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, ValueProvider} from './provider';


Expand Down Expand Up @@ -36,7 +37,7 @@ export interface ɵɵInjectableDeclaration<T> {
* - `null`, does not belong to any injector. Must be explicitly listed in the injector
* `providers`.
*/
providedIn: InjectorType<any>|'root'|'platform'|'any'|null;
providedIn: InjectorType<any>|'root'|'platform'|'any'|'env'|null;

/**
* The token to which this definition belongs.
Expand Down Expand Up @@ -141,7 +142,7 @@ export interface InjectorTypeWithProviders<T> {
*/
export function ɵɵdefineInjectable<T>(opts: {
token: unknown,
providedIn?: Type<any>|'root'|'platform'|'any'|null, factory: () => T,
providedIn?: Type<any>|'root'|'platform'|'any'|'env'|null, factory: () => T,
}): unknown {
return {
token: opts.token,
Expand Down
30 changes: 15 additions & 15 deletions packages/core/src/di/r3_injector.ts
Expand Up @@ -28,7 +28,7 @@ import {InjectFlags} from './interface/injector';
import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, Provider, StaticClassProvider, StaticProvider, TypeProvider, ValueProvider} from './interface/provider';
import {NullInjector} from './null_injector';
import {ProviderToken} from './provider_token';
import {INJECTOR_SCOPE} from './scope';
import {INJECTOR_SCOPE, InjectorScope} from './scope';

/**
* Internal type for a single provider in a deep provider array.
Expand Down Expand Up @@ -101,14 +101,15 @@ export function createInjector(
*/
export function createInjectorWithoutInjectorInstances(
defType: /* InjectorType<any> */ any, parent: Injector|null = null,
additionalProviders: StaticProvider[]|null = null, name?: string): R3Injector {
additionalProviders: StaticProvider[]|null = null, name?: string,
scopes = new Set<InjectorScope>()): R3Injector {
const providers = importProvidersFrom(defType);
if (additionalProviders) {
providers.push(...flatten(additionalProviders));
}
name = name || (typeof defType === 'object' ? undefined : stringify(defType));

return new R3Injector(providers, parent || getNullInjector(), name);
return new R3Injector(providers, parent || getNullInjector(), name || null, scopes);
}

/**
Expand Down Expand Up @@ -298,12 +299,6 @@ export class R3Injector extends EnvInjector {

private _onDestroyHooks: Array<() => void> = [];

/**
* Flag indicating this injector provides the APP_ROOT_SCOPE token, and thus counts as the
* root scope.
*/
private readonly scope: 'root'|'platform'|null;

/**
* Flag indicating that this injector was previously destroyed.
*/
Expand All @@ -315,7 +310,8 @@ export class R3Injector extends EnvInjector {
private injectorDefTypes: Set<Type<unknown>>;

constructor(
providers: Provider[], readonly parent: Injector, readonly source: string|null = null) {
providers: Provider[], readonly parent: Injector, readonly source: string|null,
readonly scopes: Set<InjectorScope>) {
super();
// Start off by creating Records for every provider.
for (const provider of providers) {
Expand All @@ -325,13 +321,17 @@ export class R3Injector extends EnvInjector {
// Make sure the INJECTOR token provides this injector.
this.records.set(INJECTOR, makeRecord(undefined, this));

// And `EnvInjector`.
this.records.set(EnvInjector, makeRecord(undefined, this));
// And `EnvInjector` if the current injector is supposed to be env-scoped.
if (scopes.has('env')) {
this.records.set(EnvInjector, makeRecord(undefined, this));
}

// Detect whether this injector has the APP_ROOT_SCOPE token and thus should provide
// any injectable scoped to APP_ROOT_SCOPE.
const record = this.records.get(INJECTOR_SCOPE);
this.scope = record != null ? record.value : null;
const record = this.records.get(INJECTOR_SCOPE) as Record<InjectorScope|null>;
if (record != null && typeof record.value === 'string') {
this.scopes.add(record.value as InjectorScope);
}

this.injectorDefTypes = new Set(this.get(INJECTOR_DEF_TYPES.multi, EMPTY_ARRAY));
}
Expand Down Expand Up @@ -518,7 +518,7 @@ export class R3Injector extends EnvInjector {
}
const providedIn = resolveForwardRef(def.providedIn);
if (typeof providedIn === 'string') {
return providedIn === 'any' || (providedIn === this.scope);
return providedIn === 'any' || (this.scopes.has(providedIn));
} else {
return this.injectorDefTypes.has(providedIn);
}
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/di/scope.ts
Expand Up @@ -9,9 +9,11 @@
import {InjectionToken} from './injection_token';


export type InjectorScope = 'root'|'platform'|'env';

/**
* An internal token whose presence in an injector indicates that the injector should treat itself
* as a root scoped injector when processing requests for unknown tokens which may indicate
* they are provided in the root scope.
*/
export const INJECTOR_SCOPE = new InjectionToken<'root'|'platform'|null>('Set Injector scope.');
export const INJECTOR_SCOPE = new InjectionToken<InjectorScope|null>('Set Injector scope.');
4 changes: 2 additions & 2 deletions packages/core/src/render3/ng_module_ref.ts
Expand Up @@ -68,7 +68,7 @@ export class NgModuleRef<T> extends viewEngine_NgModuleRef<T> implements Interna
useValue: this.componentFactoryResolver
}
],
stringify(ngModuleType)) as R3Injector;
stringify(ngModuleType), new Set(['env'])) as R3Injector;

// We need to resolve the injector types separately from the injector creation, because
// the module might be trying to use this ref in its constructor for DI which will cause a
Expand Down Expand Up @@ -121,7 +121,7 @@ class EnvNgModuleRefAdapter extends viewEngine_NgModuleRef<null> {
{provide: viewEngine_NgModuleRef, useValue: this},
{provide: viewEngine_ComponentFactoryResolver, useValue: this.componentFactoryResolver},
],
parent || getNullInjector(), source);
parent || getNullInjector(), source, new Set(['env']));
this.injector = injector;
injector.resolveInjectorInitializers();
}
Expand Down
9 changes: 9 additions & 0 deletions packages/core/test/acceptance/env_injector_spec.ts
Expand Up @@ -95,4 +95,13 @@ describe('environment injector', () => {

expect(initialized).toBeTrue();
});

it('should adopt env-scoped providers', () => {
const injector = createEnvInjector([]);
const EnvScopedToken = new InjectionToken('env-scoped token', {
providedIn: 'env' as any,
factory: () => true,
});
expect(injector.get(EnvScopedToken, false)).toBeTrue();
});
});
2 changes: 1 addition & 1 deletion packages/core/test/di/static_injector_spec.ts
Expand Up @@ -190,7 +190,7 @@ class SportsCar extends Car {
it('should work', () => {
expect(Injector.create([Engine.PROVIDER, {provide: BrokenEngine, useValue: null}]).toString())
.toEqual(
'R3Injector[Engine, BrokenEngine, InjectionToken INJECTOR, EnvInjector, InjectionToken INJECTOR_DEF_TYPES, InjectionToken INJECTOR_INITIALIZER]');
'R3Injector[Engine, BrokenEngine, InjectionToken INJECTOR, InjectionToken INJECTOR_DEF_TYPES, InjectionToken INJECTOR_INITIALIZER]');
});
});
}

0 comments on commit ee45cb9

Please sign in to comment.