feat(core): Adds DI support for providedIn: 'platform'|'any'
(#32154)
Extend the vocabulary of the `providedIn` to also include `'platform'` and `'any'`` scope. ``` @Injectable({ providedId: 'platform', // tree shakable injector for platform injector }) class MyService {...} ``` PR Close #32154
This commit is contained in:
@ -50,9 +50,11 @@ export interface InjectableDecorator {
|
||||
*
|
||||
*/
|
||||
(): TypeDecorator;
|
||||
(options?: {providedIn: Type<any>| 'root' | null}&InjectableProvider): TypeDecorator;
|
||||
(options?: {providedIn: Type<any>| 'root' | 'platform' | 'any' | null}&
|
||||
InjectableProvider): TypeDecorator;
|
||||
new (): Injectable;
|
||||
new (options?: {providedIn: Type<any>| 'root' | null}&InjectableProvider): Injectable;
|
||||
new (options?: {providedIn: Type<any>| 'root' | 'platform' | 'any' | null}&
|
||||
InjectableProvider): Injectable;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,10 +66,14 @@ export interface Injectable {
|
||||
/**
|
||||
* Determines which injectors will provide the injectable,
|
||||
* by either associating it with an @NgModule or other `InjectorType`,
|
||||
* or by specifying that this injectable should be provided in the
|
||||
* 'root' injector, which will be the application-level injector in most apps.
|
||||
* or by specifying that this injectable should be provided in the:
|
||||
* - 'root' injector, which will be the application-level injector in most apps.
|
||||
* - 'platform' injector, which would be the special singleton platform injector shared by all
|
||||
* applications on the page.
|
||||
* - 'any` injector, which would be the injector which receives the resolution. (Note this only
|
||||
* works on NgModule Injectors and not on Element Injector)
|
||||
*/
|
||||
providedIn?: Type<any>|'root'|null;
|
||||
providedIn?: Type<any>|'root'|'platform'|'any'|null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,9 +96,9 @@ export interface InjectableType<T> extends Type<T> { ngInjectableDef: ɵɵInject
|
||||
/**
|
||||
* Supports @Injectable() in JIT mode for Render2.
|
||||
*/
|
||||
function render2CompileInjectable(
|
||||
injectableType: Type<any>,
|
||||
options?: {providedIn?: Type<any>| 'root' | null} & InjectableProvider): void {
|
||||
function render2CompileInjectable(injectableType: Type<any>, options?: {
|
||||
providedIn?: Type<any>| 'root' | 'platform' | 'any' | null
|
||||
} & InjectableProvider): void {
|
||||
if (options && options.providedIn !== undefined && !getInjectableDef(injectableType)) {
|
||||
(injectableType as InjectableType<any>).ngInjectableDef = ɵɵdefineInjectable({
|
||||
token: injectableType,
|
||||
|
@ -57,7 +57,7 @@ export class InjectionToken<T> {
|
||||
readonly ngInjectableDef: never|undefined;
|
||||
|
||||
constructor(protected _desc: string, options?: {
|
||||
providedIn?: Type<any>| 'root' | null,
|
||||
providedIn?: Type<any>| 'root' | 'platform' | 'any' | null,
|
||||
factory: () => T
|
||||
}) {
|
||||
this.ngInjectableDef = undefined;
|
||||
|
@ -11,12 +11,13 @@ import {stringify} from '../util/stringify';
|
||||
|
||||
import {resolveForwardRef} from './forward_ref';
|
||||
import {InjectionToken} from './injection_token';
|
||||
import {INJECTOR, NG_TEMP_TOKEN_PATH, NullInjector, THROW_IF_NOT_FOUND, USE_VALUE, catchInjectorError, formatError, ɵɵinject} from './injector_compatibility';
|
||||
import {ɵɵdefineInjectable} from './interface/defs';
|
||||
import {INJECTOR, NG_TEMP_TOKEN_PATH, NullInjector, THROW_IF_NOT_FOUND, USE_VALUE, catchInjectorError, formatError, setCurrentInjector, ɵɵinject} from './injector_compatibility';
|
||||
import {getInjectableDef, ɵɵdefineInjectable} from './interface/defs';
|
||||
import {InjectFlags} from './interface/injector';
|
||||
import {ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, StaticProvider, ValueProvider} from './interface/provider';
|
||||
import {Inject, Optional, Self, SkipSelf} from './metadata';
|
||||
import {createInjector} from './r3_injector';
|
||||
import {INJECTOR_SCOPE} from './scope';
|
||||
|
||||
export function INJECTOR_IMPL__PRE_R3__(
|
||||
providers: StaticProvider[], parent: Injector | undefined, name: string) {
|
||||
@ -124,8 +125,9 @@ const NO_NEW_LINE = 'ɵ';
|
||||
export class StaticInjector implements Injector {
|
||||
readonly parent: Injector;
|
||||
readonly source: string|null;
|
||||
readonly scope: string|null;
|
||||
|
||||
private _records: Map<any, Record>;
|
||||
private _records: Map<any, Record|null>;
|
||||
|
||||
constructor(
|
||||
providers: StaticProvider[], parent: Injector = Injector.NULL, source: string|null = null) {
|
||||
@ -136,17 +138,37 @@ export class StaticInjector implements Injector {
|
||||
Injector, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false});
|
||||
records.set(
|
||||
INJECTOR, <Record>{token: INJECTOR, fn: IDENT, deps: EMPTY, value: this, useNew: false});
|
||||
recursivelyProcessProviders(records, providers);
|
||||
this.scope = recursivelyProcessProviders(records, providers);
|
||||
}
|
||||
|
||||
get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
|
||||
get(token: any, notFoundValue?: any): any;
|
||||
get(token: any, notFoundValue?: any, flags: InjectFlags = InjectFlags.Default): any {
|
||||
const record = this._records.get(token);
|
||||
const records = this._records;
|
||||
let record = records.get(token);
|
||||
if (record === undefined) {
|
||||
// This means we have never seen this record, see if it is tree shakable provider.
|
||||
const injectableDef = getInjectableDef(token);
|
||||
if (injectableDef) {
|
||||
const providedIn = injectableDef && injectableDef.providedIn;
|
||||
if (providedIn === 'any' || providedIn != null && providedIn === this.scope) {
|
||||
records.set(
|
||||
token, record = resolveProvider(
|
||||
{provide: token, useFactory: injectableDef.factory, deps: EMPTY}));
|
||||
}
|
||||
}
|
||||
if (record === undefined) {
|
||||
// Set record to null to make sure that we don't go through expensive lookup above again.
|
||||
records.set(token, null);
|
||||
}
|
||||
}
|
||||
let lastInjector = setCurrentInjector(this);
|
||||
try {
|
||||
return tryResolveToken(token, record, this._records, this.parent, notFoundValue, flags);
|
||||
return tryResolveToken(token, record, records, this.parent, notFoundValue, flags);
|
||||
} catch (e) {
|
||||
return catchInjectorError(e, token, 'StaticInjectorError', this.source);
|
||||
} finally {
|
||||
setCurrentInjector(lastInjector);
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,13 +225,15 @@ function multiProviderMixError(token: any) {
|
||||
return staticError('Cannot mix multi providers and regular providers', token);
|
||||
}
|
||||
|
||||
function recursivelyProcessProviders(records: Map<any, Record>, provider: StaticProvider) {
|
||||
function recursivelyProcessProviders(records: Map<any, Record>, provider: StaticProvider): string|
|
||||
null {
|
||||
let scope: string|null = null;
|
||||
if (provider) {
|
||||
provider = resolveForwardRef(provider);
|
||||
if (provider instanceof Array) {
|
||||
// if we have an array recurse into the array
|
||||
for (let i = 0; i < provider.length; i++) {
|
||||
recursivelyProcessProviders(records, provider[i]);
|
||||
scope = recursivelyProcessProviders(records, provider[i]) || scope;
|
||||
}
|
||||
} else if (typeof provider === 'function') {
|
||||
// Functions were supported in ReflectiveInjector, but are not here. For safety give useful
|
||||
@ -244,15 +268,19 @@ function recursivelyProcessProviders(records: Map<any, Record>, provider: Static
|
||||
if (record && record.fn == MULTI_PROVIDER_FN) {
|
||||
throw multiProviderMixError(token);
|
||||
}
|
||||
if (token === INJECTOR_SCOPE) {
|
||||
scope = resolvedProvider.value;
|
||||
}
|
||||
records.set(token, resolvedProvider);
|
||||
} else {
|
||||
throw staticError('Unexpected provider', provider);
|
||||
}
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
function tryResolveToken(
|
||||
token: any, record: Record | undefined, records: Map<any, Record>, parent: Injector,
|
||||
token: any, record: Record | undefined | null, records: Map<any, Record|null>, parent: Injector,
|
||||
notFoundValue: any, flags: InjectFlags): any {
|
||||
try {
|
||||
return resolveToken(token, record, records, parent, notFoundValue, flags);
|
||||
@ -272,7 +300,7 @@ function tryResolveToken(
|
||||
}
|
||||
|
||||
function resolveToken(
|
||||
token: any, record: Record | undefined, records: Map<any, Record>, parent: Injector,
|
||||
token: any, record: Record | undefined | null, records: Map<any, Record|null>, parent: Injector,
|
||||
notFoundValue: any, flags: InjectFlags): any {
|
||||
let value;
|
||||
if (record && !(flags & InjectFlags.SkipSelf)) {
|
||||
|
@ -35,7 +35,7 @@ export interface ɵɵInjectableDef<T> {
|
||||
* - `null`, does not belong to any injector. Must be explicitly listed in the injector
|
||||
* `providers`.
|
||||
*/
|
||||
providedIn: InjectorType<any>|'root'|'any'|null;
|
||||
providedIn: InjectorType<any>|'root'|'platform'|'any'|null;
|
||||
|
||||
/**
|
||||
* The token to which this definition belongs.
|
||||
@ -140,7 +140,7 @@ export interface InjectorTypeWithProviders<T> {
|
||||
*/
|
||||
export function ɵɵdefineInjectable<T>(opts: {
|
||||
token: unknown,
|
||||
providedIn?: Type<any>| 'root' | 'any' | null,
|
||||
providedIn?: Type<any>| 'root' | 'platform' | 'any' | null,
|
||||
factory: () => T,
|
||||
}): never {
|
||||
return ({
|
||||
|
@ -21,7 +21,7 @@ import {INJECTOR, NG_TEMP_TOKEN_PATH, NullInjector, THROW_IF_NOT_FOUND, USE_VALU
|
||||
import {InjectorType, InjectorTypeWithProviders, getInheritedInjectableDef, getInjectableDef, getInjectorDef, ɵɵInjectableDef} from './interface/defs';
|
||||
import {InjectFlags} from './interface/injector';
|
||||
import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, StaticProvider, TypeProvider, ValueProvider} from './interface/provider';
|
||||
import {APP_ROOT} from './scope';
|
||||
import {INJECTOR_SCOPE} from './scope';
|
||||
|
||||
|
||||
|
||||
@ -84,8 +84,10 @@ export function createInjector(
|
||||
export class R3Injector {
|
||||
/**
|
||||
* Map of tokens to records which contain the instances of those tokens.
|
||||
* - `null` value implies that we don't have the record. Used by tree-shakable injectors
|
||||
* to prevent further searches.
|
||||
*/
|
||||
private records = new Map<Type<any>|InjectionToken<any>, Record<any>>();
|
||||
private records = new Map<Type<any>|InjectionToken<any>, Record<any>|null>();
|
||||
|
||||
/**
|
||||
* The transitive set of `InjectorType`s which define this injector.
|
||||
@ -101,7 +103,7 @@ export class R3Injector {
|
||||
* Flag indicating this injector provides the APP_ROOT_SCOPE token, and thus counts as the
|
||||
* root scope.
|
||||
*/
|
||||
private readonly isRootInjector: boolean;
|
||||
private readonly scope: 'root'|'platform'|null;
|
||||
|
||||
readonly source: string|null;
|
||||
|
||||
@ -129,7 +131,8 @@ export class R3Injector {
|
||||
|
||||
// Detect whether this injector has the APP_ROOT_SCOPE token and thus should provide
|
||||
// any injectable scoped to APP_ROOT_SCOPE.
|
||||
this.isRootInjector = this.records.has(APP_ROOT);
|
||||
const record = this.records.get(INJECTOR_SCOPE);
|
||||
this.scope = record != null ? record.value : null;
|
||||
|
||||
// Eagerly instantiate the InjectorType classes themselves.
|
||||
this.injectorDefTypes.forEach(defType => this.get(defType));
|
||||
@ -170,7 +173,7 @@ export class R3Injector {
|
||||
// Check for the SkipSelf flag.
|
||||
if (!(flags & InjectFlags.SkipSelf)) {
|
||||
// SkipSelf isn't set, check if the record belongs to this injector.
|
||||
let record: Record<T>|undefined = this.records.get(token);
|
||||
let record: Record<T>|undefined|null = this.records.get(token);
|
||||
if (record === undefined) {
|
||||
// No record, but maybe the token is scoped to this injector. Look for an ngInjectableDef
|
||||
// with a scope matching this injector.
|
||||
@ -179,11 +182,13 @@ export class R3Injector {
|
||||
// Found an ngInjectableDef and it's scoped to this injector. Pretend as if it was here
|
||||
// all along.
|
||||
record = makeRecord(injectableDefOrInjectorDefFactory(token), NOT_YET);
|
||||
this.records.set(token, record);
|
||||
} else {
|
||||
record = null;
|
||||
}
|
||||
this.records.set(token, record);
|
||||
}
|
||||
// If a record was found, get the instance for it and return it.
|
||||
if (record !== undefined) {
|
||||
if (record != null /* NOT null || undefined */) {
|
||||
return this.hydrate(token, record);
|
||||
}
|
||||
}
|
||||
@ -389,7 +394,7 @@ export class R3Injector {
|
||||
if (!def.providedIn) {
|
||||
return false;
|
||||
} else if (typeof def.providedIn === 'string') {
|
||||
return def.providedIn === 'any' || (def.providedIn === 'root' && this.isRootInjector);
|
||||
return def.providedIn === 'any' || (def.providedIn === this.scope);
|
||||
} else {
|
||||
return this.injectorDefTypes.has(def.providedIn);
|
||||
}
|
||||
|
@ -14,5 +14,4 @@ import {InjectionToken} from './injection_token';
|
||||
* as a root scoped injector when processing requests for unknown tokens which may indicate
|
||||
* they are provided in the root scope.
|
||||
*/
|
||||
export const APP_ROOT = new InjectionToken<boolean>(
|
||||
'The presence of this token marks an injector as being the root injector.');
|
||||
export const INJECTOR_SCOPE = new InjectionToken<'root'|'platform'|null>('Set Injector scope.');
|
||||
|
Reference in New Issue
Block a user