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
`EnvironmentInjector`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 d54e364 commit ca6934c
Show file tree
Hide file tree
Showing 7 changed files with 28 additions and 22 deletions.
2 changes: 1 addition & 1 deletion goldens/public-api/core/index.md
Expand Up @@ -1432,7 +1432,7 @@ export abstract class ViewRef extends ChangeDetectorRef {
// @public
export function ɵɵdefineInjectable<T>(opts: {
token: unknown;
providedIn?: Type<any> | 'root' | 'platform' | 'any' | null;
providedIn?: Type<any> | 'root' | 'platform' | 'any' | 'env' | null;
factory: () => T;
}): unknown;

Expand Down
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 = [
...flatten(additionalProviders || EMPTY_ARRAY),
...importProvidersFrom(defType),
];
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 EnvironmentInjector {

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 EnvironmentInjector {
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 EnvironmentInjector {
// Make sure the INJECTOR token provides this injector.
this.records.set(INJECTOR, makeRecord(undefined, this));

// And `EnvironmentInjector`.
this.records.set(EnvironmentInjector, makeRecord(undefined, this));
// And `EnvironmentInjector` if the current injector is supposed to be env-scoped.
if (scopes.has('env')) {
this.records.set(EnvironmentInjector, 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, InjectFlags.Self));
Expand Down Expand Up @@ -519,7 +519,7 @@ export class R3Injector extends EnvironmentInjector {
}
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 @@ -122,7 +122,7 @@ class EnvironmentNgModuleRefAdapter 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
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, EnvironmentInjector, InjectionToken INJECTOR_DEF_TYPES, InjectionToken INJECTOR_INITIALIZER]');
'R3Injector[Engine, BrokenEngine, InjectionToken INJECTOR, InjectionToken INJECTOR_DEF_TYPES, InjectionToken INJECTOR_INITIALIZER]');
});
});
}

0 comments on commit ca6934c

Please sign in to comment.