feat(core): Adds DI support for providedIn: 'platform'|'any' ()

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 
This commit is contained in:
Misko Hevery 2019-08-22 19:19:41 -07:00
parent 8a47b48912
commit 77c382ccba
16 changed files with 138 additions and 64 deletions

@ -15,7 +15,7 @@ export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetection
export {Console as ɵConsole} from './console'; export {Console as ɵConsole} from './console';
export {inject, setCurrentInjector as ɵsetCurrentInjector, ɵɵinject} from './di/injector_compatibility'; export {inject, setCurrentInjector as ɵsetCurrentInjector, ɵɵinject} from './di/injector_compatibility';
export {getInjectableDef as ɵgetInjectableDef, ɵɵInjectableDef, ɵɵInjectorDef} from './di/interface/defs'; export {getInjectableDef as ɵgetInjectableDef, ɵɵInjectableDef, ɵɵInjectorDef} from './di/interface/defs';
export {APP_ROOT as ɵAPP_ROOT} from './di/scope'; export {INJECTOR_SCOPE as ɵINJECTOR_SCOPE} from './di/scope';
export {DEFAULT_LOCALE_ID as ɵDEFAULT_LOCALE_ID} from './i18n/localization'; export {DEFAULT_LOCALE_ID as ɵDEFAULT_LOCALE_ID} from './i18n/localization';
export {ivyEnabled as ɵivyEnabled} from './ivy_switch'; export {ivyEnabled as ɵivyEnabled} from './ivy_switch';
export {ComponentFactory as ɵComponentFactory} from './linker/component_factory'; export {ComponentFactory as ɵComponentFactory} from './linker/component_factory';

@ -50,9 +50,11 @@ export interface InjectableDecorator {
* *
*/ */
(): TypeDecorator; (): TypeDecorator;
(options?: {providedIn: Type<any>| 'root' | null}&InjectableProvider): TypeDecorator; (options?: {providedIn: Type<any>| 'root' | 'platform' | 'any' | null}&
InjectableProvider): TypeDecorator;
new (): Injectable; 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, * Determines which injectors will provide the injectable,
* by either associating it with an @NgModule or other `InjectorType`, * by either associating it with an @NgModule or other `InjectorType`,
* or by specifying that this injectable should be provided in the * or by specifying that this injectable should be provided in the:
* 'root' injector, which will be the application-level injector in most apps. * - '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. * Supports @Injectable() in JIT mode for Render2.
*/ */
function render2CompileInjectable( function render2CompileInjectable(injectableType: Type<any>, options?: {
injectableType: Type<any>, providedIn?: Type<any>| 'root' | 'platform' | 'any' | null
options?: {providedIn?: Type<any>| 'root' | null} & InjectableProvider): void { } & InjectableProvider): void {
if (options && options.providedIn !== undefined && !getInjectableDef(injectableType)) { if (options && options.providedIn !== undefined && !getInjectableDef(injectableType)) {
(injectableType as InjectableType<any>).ngInjectableDef = ɵɵdefineInjectable({ (injectableType as InjectableType<any>).ngInjectableDef = ɵɵdefineInjectable({
token: injectableType, token: injectableType,

@ -57,7 +57,7 @@ export class InjectionToken<T> {
readonly ngInjectableDef: never|undefined; readonly ngInjectableDef: never|undefined;
constructor(protected _desc: string, options?: { constructor(protected _desc: string, options?: {
providedIn?: Type<any>| 'root' | null, providedIn?: Type<any>| 'root' | 'platform' | 'any' | null,
factory: () => T factory: () => T
}) { }) {
this.ngInjectableDef = undefined; this.ngInjectableDef = undefined;

@ -11,12 +11,13 @@ import {stringify} from '../util/stringify';
import {resolveForwardRef} from './forward_ref'; import {resolveForwardRef} from './forward_ref';
import {InjectionToken} from './injection_token'; 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 {INJECTOR, NG_TEMP_TOKEN_PATH, NullInjector, THROW_IF_NOT_FOUND, USE_VALUE, catchInjectorError, formatError, setCurrentInjector, ɵɵinject} from './injector_compatibility';
import {ɵɵdefineInjectable} from './interface/defs'; import {getInjectableDef, ɵɵdefineInjectable} from './interface/defs';
import {InjectFlags} from './interface/injector'; import {InjectFlags} from './interface/injector';
import {ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, StaticProvider, ValueProvider} from './interface/provider'; import {ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, StaticProvider, ValueProvider} from './interface/provider';
import {Inject, Optional, Self, SkipSelf} from './metadata'; import {Inject, Optional, Self, SkipSelf} from './metadata';
import {createInjector} from './r3_injector'; import {createInjector} from './r3_injector';
import {INJECTOR_SCOPE} from './scope';
export function INJECTOR_IMPL__PRE_R3__( export function INJECTOR_IMPL__PRE_R3__(
providers: StaticProvider[], parent: Injector | undefined, name: string) { providers: StaticProvider[], parent: Injector | undefined, name: string) {
@ -124,8 +125,9 @@ const NO_NEW_LINE = 'ɵ';
export class StaticInjector implements Injector { export class StaticInjector implements Injector {
readonly parent: Injector; readonly parent: Injector;
readonly source: string|null; readonly source: string|null;
readonly scope: string|null;
private _records: Map<any, Record>; private _records: Map<any, Record|null>;
constructor( constructor(
providers: StaticProvider[], parent: Injector = Injector.NULL, source: string|null = null) { 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}); Injector, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false});
records.set( records.set(
INJECTOR, <Record>{token: INJECTOR, fn: IDENT, deps: EMPTY, value: this, useNew: false}); 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<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
get(token: any, notFoundValue?: any): any; get(token: any, notFoundValue?: any): any;
get(token: any, notFoundValue?: any, flags: InjectFlags = InjectFlags.Default): 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 { try {
return tryResolveToken(token, record, this._records, this.parent, notFoundValue, flags); return tryResolveToken(token, record, records, this.parent, notFoundValue, flags);
} catch (e) { } catch (e) {
return catchInjectorError(e, token, 'StaticInjectorError', this.source); 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); 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) { if (provider) {
provider = resolveForwardRef(provider); provider = resolveForwardRef(provider);
if (provider instanceof Array) { if (provider instanceof Array) {
// if we have an array recurse into the array // if we have an array recurse into the array
for (let i = 0; i < provider.length; i++) { for (let i = 0; i < provider.length; i++) {
recursivelyProcessProviders(records, provider[i]); scope = recursivelyProcessProviders(records, provider[i]) || scope;
} }
} else if (typeof provider === 'function') { } else if (typeof provider === 'function') {
// Functions were supported in ReflectiveInjector, but are not here. For safety give useful // 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) { if (record && record.fn == MULTI_PROVIDER_FN) {
throw multiProviderMixError(token); throw multiProviderMixError(token);
} }
if (token === INJECTOR_SCOPE) {
scope = resolvedProvider.value;
}
records.set(token, resolvedProvider); records.set(token, resolvedProvider);
} else { } else {
throw staticError('Unexpected provider', provider); throw staticError('Unexpected provider', provider);
} }
} }
return scope;
} }
function tryResolveToken( 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 { notFoundValue: any, flags: InjectFlags): any {
try { try {
return resolveToken(token, record, records, parent, notFoundValue, flags); return resolveToken(token, record, records, parent, notFoundValue, flags);
@ -272,7 +300,7 @@ function tryResolveToken(
} }
function resolveToken( 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 { notFoundValue: any, flags: InjectFlags): any {
let value; let value;
if (record && !(flags & InjectFlags.SkipSelf)) { 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 * - `null`, does not belong to any injector. Must be explicitly listed in the injector
* `providers`. * `providers`.
*/ */
providedIn: InjectorType<any>|'root'|'any'|null; providedIn: InjectorType<any>|'root'|'platform'|'any'|null;
/** /**
* The token to which this definition belongs. * The token to which this definition belongs.
@ -140,7 +140,7 @@ export interface InjectorTypeWithProviders<T> {
*/ */
export function ɵɵdefineInjectable<T>(opts: { export function ɵɵdefineInjectable<T>(opts: {
token: unknown, token: unknown,
providedIn?: Type<any>| 'root' | 'any' | null, providedIn?: Type<any>| 'root' | 'platform' | 'any' | null,
factory: () => T, factory: () => T,
}): never { }): never {
return ({ 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 {InjectorType, InjectorTypeWithProviders, getInheritedInjectableDef, getInjectableDef, getInjectorDef, ɵɵInjectableDef} from './interface/defs';
import {InjectFlags} from './interface/injector'; import {InjectFlags} from './interface/injector';
import {ClassProvider, ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, StaticProvider, TypeProvider, ValueProvider} from './interface/provider'; 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 { export class R3Injector {
/** /**
* Map of tokens to records which contain the instances of those tokens. * 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. * 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 * Flag indicating this injector provides the APP_ROOT_SCOPE token, and thus counts as the
* root scope. * root scope.
*/ */
private readonly isRootInjector: boolean; private readonly scope: 'root'|'platform'|null;
readonly source: string|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 // Detect whether this injector has the APP_ROOT_SCOPE token and thus should provide
// any injectable scoped to APP_ROOT_SCOPE. // 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. // Eagerly instantiate the InjectorType classes themselves.
this.injectorDefTypes.forEach(defType => this.get(defType)); this.injectorDefTypes.forEach(defType => this.get(defType));
@ -170,7 +173,7 @@ export class R3Injector {
// Check for the SkipSelf flag. // Check for the SkipSelf flag.
if (!(flags & InjectFlags.SkipSelf)) { if (!(flags & InjectFlags.SkipSelf)) {
// SkipSelf isn't set, check if the record belongs to this injector. // 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) { if (record === undefined) {
// No record, but maybe the token is scoped to this injector. Look for an ngInjectableDef // No record, but maybe the token is scoped to this injector. Look for an ngInjectableDef
// with a scope matching this injector. // 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 // Found an ngInjectableDef and it's scoped to this injector. Pretend as if it was here
// all along. // all along.
record = makeRecord(injectableDefOrInjectorDefFactory(token), NOT_YET); 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 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); return this.hydrate(token, record);
} }
} }
@ -389,7 +394,7 @@ export class R3Injector {
if (!def.providedIn) { if (!def.providedIn) {
return false; return false;
} else if (typeof def.providedIn === 'string') { } else if (typeof def.providedIn === 'string') {
return def.providedIn === 'any' || (def.providedIn === 'root' && this.isRootInjector); return def.providedIn === 'any' || (def.providedIn === this.scope);
} else { } else {
return this.injectorDefTypes.has(def.providedIn); 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 * as a root scoped injector when processing requests for unknown tokens which may indicate
* they are provided in the root scope. * they are provided in the root scope.
*/ */
export const APP_ROOT = new InjectionToken<boolean>( export const INJECTOR_SCOPE = new InjectionToken<'root'|'platform'|null>('Set Injector scope.');
'The presence of this token marks an injector as being the root injector.');

@ -48,7 +48,7 @@ function cloneNgModuleDefinition(def: NgModuleDefinition): NgModuleDefinition {
return { return {
factory: def.factory, factory: def.factory,
isRoot: def.isRoot, providers, modules, providersByKey, scope: def.scope, providers, modules, providersByKey,
}; };
} }

@ -10,7 +10,7 @@ import {resolveForwardRef} from '../di/forward_ref';
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {INJECTOR, setCurrentInjector} from '../di/injector_compatibility'; import {INJECTOR, setCurrentInjector} from '../di/injector_compatibility';
import {getInjectableDef, ɵɵInjectableDef} from '../di/interface/defs'; import {getInjectableDef, ɵɵInjectableDef} from '../di/interface/defs';
import {APP_ROOT} from '../di/scope'; import {INJECTOR_SCOPE} from '../di/scope';
import {NgModuleRef} from '../linker/ng_module_factory'; import {NgModuleRef} from '../linker/ng_module_factory';
import {newArray} from '../util/array_utils'; import {newArray} from '../util/array_utils';
import {stringify} from '../util/stringify'; import {stringify} from '../util/stringify';
@ -42,11 +42,11 @@ export function moduleProvideDef(
export function moduleDef(providers: NgModuleProviderDef[]): NgModuleDefinition { export function moduleDef(providers: NgModuleProviderDef[]): NgModuleDefinition {
const providersByKey: {[key: string]: NgModuleProviderDef} = {}; const providersByKey: {[key: string]: NgModuleProviderDef} = {};
const modules = []; const modules = [];
let isRoot: boolean = false; let scope: 'root'|'platform'|null = null;
for (let i = 0; i < providers.length; i++) { for (let i = 0; i < providers.length; i++) {
const provider = providers[i]; const provider = providers[i];
if (provider.token === APP_ROOT && provider.value === true) { if (provider.token === INJECTOR_SCOPE) {
isRoot = true; scope = provider.value;
} }
if (provider.flags & NodeFlags.TypeNgModule) { if (provider.flags & NodeFlags.TypeNgModule) {
modules.push(provider.token); modules.push(provider.token);
@ -60,7 +60,7 @@ export function moduleDef(providers: NgModuleProviderDef[]): NgModuleDefinition
providersByKey, providersByKey,
providers, providers,
modules, modules,
isRoot, scope: scope,
}; };
} }
@ -134,8 +134,9 @@ function moduleTransitivelyPresent(ngModule: NgModuleData, scope: any): boolean
} }
function targetsModule(ngModule: NgModuleData, def: ɵɵInjectableDef<any>): boolean { function targetsModule(ngModule: NgModuleData, def: ɵɵInjectableDef<any>): boolean {
return def.providedIn != null && (moduleTransitivelyPresent(ngModule, def.providedIn) || const providedIn = def.providedIn;
def.providedIn === 'root' && ngModule._def.isRoot); return providedIn != null && (providedIn === 'any' || providedIn === ngModule._def.scope ||
moduleTransitivelyPresent(ngModule, providedIn));
} }
function _createProviderInstance(ngModule: NgModuleData, providerDef: NgModuleProviderDef): any { function _createProviderInstance(ngModule: NgModuleData, providerDef: NgModuleProviderDef): any {

@ -45,7 +45,7 @@ export interface NgModuleDefinition extends Definition<NgModuleDefinitionFactory
providers: NgModuleProviderDef[]; providers: NgModuleProviderDef[];
providersByKey: {[tokenKey: string]: NgModuleProviderDef}; providersByKey: {[tokenKey: string]: NgModuleProviderDef};
modules: any[]; modules: any[];
isRoot: boolean; scope: 'root'|'platform'|null;
} }
export interface NgModuleDefinitionFactory extends DefinitionFactory<NgModuleDefinition> {} export interface NgModuleDefinitionFactory extends DefinitionFactory<NgModuleDefinition> {}

@ -8,6 +8,7 @@
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {Attribute, ChangeDetectorRef, Component, Directive, ElementRef, EventEmitter, Host, HostBinding, INJECTOR, Inject, Injectable, InjectionToken, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, Optional, Output, Pipe, PipeTransform, Self, SkipSelf, TemplateRef, ViewChild, ViewContainerRef, forwardRef, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID} from '@angular/core'; import {Attribute, ChangeDetectorRef, Component, Directive, ElementRef, EventEmitter, Host, HostBinding, INJECTOR, Inject, Injectable, InjectionToken, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, Optional, Output, Pipe, PipeTransform, Self, SkipSelf, TemplateRef, ViewChild, ViewContainerRef, forwardRef, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID} from '@angular/core';
import {ɵINJECTOR_SCOPE} from '@angular/core/src/core';
import {ViewRef} from '@angular/core/src/render3/view_ref'; import {ViewRef} from '@angular/core/src/render3/view_ref';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
import {ivyEnabled, onlyInIvy} from '@angular/private/testing'; import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
@ -866,6 +867,37 @@ describe('di', () => {
}); });
}); });
describe('Tree shakable injectors', () => {
it('should support tree shakable injectors scopes', () => {
@Injectable({providedIn: 'any'})
class AnyService {
constructor(public injector: Injector) {}
}
@Injectable({providedIn: 'root'})
class RootService {
constructor(public injector: Injector) {}
}
@Injectable({providedIn: 'platform'})
class PlatformService {
constructor(public injector: Injector) {}
}
const testBedInjector: Injector = TestBed.get(Injector);
const childInjector = Injector.create([], testBedInjector);
const anyService = childInjector.get(AnyService);
expect(anyService.injector).toBe(childInjector);
const rootService = childInjector.get(RootService);
expect(rootService.injector.get(ɵINJECTOR_SCOPE)).toBe('root');
const platformService = childInjector.get(PlatformService);
expect(platformService.injector.get(ɵINJECTOR_SCOPE)).toBe('platform');
});
});
describe('service injection', () => { describe('service injection', () => {
it('should create instance even when no injector present', () => { it('should create instance even when no injector present', () => {

@ -1,6 +1,6 @@
[ [
{ {
"name": "APP_ROOT" "name": "INJECTOR_SCOPE"
}, },
{ {
"name": "CIRCULAR" "name": "CIRCULAR"

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {NgModuleRef} from '@angular/core'; import {NgModuleRef, ɵINJECTOR_SCOPE as INJECTOR_SCOPE} from '@angular/core';
import {InjectFlags, inject} from '@angular/core/src/di'; import {InjectFlags, inject} from '@angular/core/src/di';
import {Injector} from '@angular/core/src/di/injector'; import {Injector} from '@angular/core/src/di/injector';
import {INJECTOR} from '@angular/core/src/di/injector_compatibility'; import {INJECTOR} from '@angular/core/src/di/injector_compatibility';
@ -16,8 +16,6 @@ import {moduleDef} from '@angular/core/src/view/ng_module';
import {createNgModuleRef} from '@angular/core/src/view/refs'; import {createNgModuleRef} from '@angular/core/src/view/refs';
import {tokenKey} from '@angular/core/src/view/util'; import {tokenKey} from '@angular/core/src/view/util';
import {APP_ROOT} from '../../src/di/scope';
class Foo {} class Foo {}
class MyModule {} class MyModule {}
@ -133,7 +131,7 @@ function makeFactoryProviders(
function makeModule(modules: any[], providers: NgModuleProviderDef[]): NgModuleDefinition { function makeModule(modules: any[], providers: NgModuleProviderDef[]): NgModuleDefinition {
const providersByKey: {[key: string]: NgModuleProviderDef} = {}; const providersByKey: {[key: string]: NgModuleProviderDef} = {};
providers.forEach(provider => providersByKey[tokenKey(provider.token)] = provider); providers.forEach(provider => providersByKey[tokenKey(provider.token)] = provider);
return {factory: null, providers, providersByKey, modules, isRoot: true}; return {factory: null, providers, providersByKey, modules, scope: 'root'};
} }
describe('NgModuleRef_ injector', () => { describe('NgModuleRef_ injector', () => {
@ -273,19 +271,24 @@ describe('NgModuleRef_ injector', () => {
}; };
} }
it('sets isRoot to `true` when APP_ROOT is `true`', () => { it('sets scope to `root` when INJECTOR_SCOPE is `root`', () => {
const def = moduleDef([createProvider(APP_ROOT, true)]); const def = moduleDef([createProvider(INJECTOR_SCOPE, 'root')]);
expect(def.isRoot).toBe(true); expect(def.scope).toBe('root');
}); });
it('sets isRoot to `false` when APP_ROOT is absent', () => { it('sets scope to `platform` when INJECTOR_SCOPE is `platform`', () => {
const def = moduleDef([createProvider(INJECTOR_SCOPE, 'platform')]);
expect(def.scope).toBe('platform');
});
it('sets scope to `null` when INJECTOR_SCOPE is absent', () => {
const def = moduleDef([]); const def = moduleDef([]);
expect(def.isRoot).toBe(false); expect(def.scope).toBe(null);
}); });
it('sets isRoot to `false` when APP_ROOT is `false`', () => { it('sets isRoot to `null` when INJECTOR_SCOPE is `null`', () => {
const def = moduleDef([createProvider(APP_ROOT, false)]); const def = moduleDef([createProvider(INJECTOR_SCOPE, null)]);
expect(def.isRoot).toBe(false); expect(def.scope).toBe(null);
}); });
}); });
}); });

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AbstractType, ApplicationInitStatus, CompilerOptions, Component, Directive, InjectFlags, InjectionToken, Injector, NgModule, NgModuleFactory, NgModuleRef, NgZone, Optional, Pipe, PlatformRef, Provider, SchemaMetadata, SkipSelf, StaticProvider, Type, ɵAPP_ROOT as APP_ROOT, ɵDepFlags as DepFlags, ɵNodeFlags as NodeFlags, ɵclearOverrides as clearOverrides, ɵgetInjectableDef as getInjectableDef, ɵivyEnabled as ivyEnabled, ɵoverrideComponentView as overrideComponentView, ɵoverrideProvider as overrideProvider, ɵstringify as stringify, ɵɵInjectableDef} from '@angular/core'; import {AbstractType, ApplicationInitStatus, CompilerOptions, Component, Directive, InjectFlags, InjectionToken, Injector, NgModule, NgModuleFactory, NgModuleRef, NgZone, Optional, Pipe, PlatformRef, Provider, SchemaMetadata, SkipSelf, StaticProvider, Type, ɵDepFlags as DepFlags, ɵINJECTOR_SCOPE as INJECTOR_SCOPE, ɵNodeFlags as NodeFlags, ɵclearOverrides as clearOverrides, ɵgetInjectableDef as getInjectableDef, ɵivyEnabled as ivyEnabled, ɵoverrideComponentView as overrideComponentView, ɵoverrideProvider as overrideProvider, ɵstringify as stringify, ɵɵInjectableDef} from '@angular/core';
import {AsyncTestCompleter} from './async_test_completer'; import {AsyncTestCompleter} from './async_test_completer';
import {ComponentFixture} from './component_fixture'; import {ComponentFixture} from './component_fixture';
@ -434,7 +434,7 @@ export class TestBedViewEngine implements TestBed {
} }
rootScopeImports.push(RootScopeModule); rootScopeImports.push(RootScopeModule);
} }
providers.push({provide: APP_ROOT, useValue: this._isRoot}); providers.push({provide: INJECTOR_SCOPE, useValue: this._isRoot ? 'root' : null});
const imports = [rootScopeImports, this.ngModule, this._imports]; const imports = [rootScopeImports, this.ngModule, this._imports];
const schemas = this._schemas; const schemas = this._schemas;

@ -7,7 +7,7 @@
*/ */
import {CommonModule, DOCUMENT, PlatformLocation, ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common'; import {CommonModule, DOCUMENT, PlatformLocation, ɵPLATFORM_BROWSER_ID as PLATFORM_BROWSER_ID} from '@angular/common';
import {APP_ID, ApplicationModule, ErrorHandler, Inject, ModuleWithProviders, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, RendererFactory2, Sanitizer, SkipSelf, StaticProvider, Testability, createPlatformFactory, platformCore, ɵAPP_ROOT as APP_ROOT, ɵConsole as Console} from '@angular/core'; import {APP_ID, ApplicationModule, ErrorHandler, Inject, ModuleWithProviders, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, RendererFactory2, Sanitizer, SkipSelf, StaticProvider, Testability, createPlatformFactory, platformCore, ɵConsole as Console, ɵINJECTOR_SCOPE as INJECTOR_SCOPE} from '@angular/core';
import {BrowserDomAdapter} from './browser/browser_adapter'; import {BrowserDomAdapter} from './browser/browser_adapter';
import {BrowserPlatformLocation} from './browser/location/browser_platform_location'; import {BrowserPlatformLocation} from './browser/location/browser_platform_location';
@ -65,7 +65,7 @@ export function _document(): any {
export const BROWSER_MODULE_PROVIDERS: StaticProvider[] = [ export const BROWSER_MODULE_PROVIDERS: StaticProvider[] = [
BROWSER_SANITIZATION_PROVIDERS, BROWSER_SANITIZATION_PROVIDERS,
{provide: APP_ROOT, useValue: true}, {provide: INJECTOR_SCOPE, useValue: 'root'},
{provide: ErrorHandler, useFactory: errorHandler, deps: []}, {provide: ErrorHandler, useFactory: errorHandler, deps: []},
{ {
provide: EVENT_MANAGER_PLUGINS, provide: EVENT_MANAGER_PLUGINS,

@ -410,7 +410,7 @@ export interface Inject {
export declare const Inject: InjectDecorator; export declare const Inject: InjectDecorator;
export interface Injectable { export interface Injectable {
providedIn?: Type<any> | 'root' | null; providedIn?: Type<any> | 'root' | 'platform' | 'any' | null;
} }
export declare const Injectable: InjectableDecorator; export declare const Injectable: InjectableDecorator;
@ -418,11 +418,11 @@ export declare const Injectable: InjectableDecorator;
export interface InjectableDecorator { export interface InjectableDecorator {
(): TypeDecorator; (): TypeDecorator;
(options?: { (options?: {
providedIn: Type<any> | 'root' | null; providedIn: Type<any> | 'root' | 'platform' | 'any' | null;
} & InjectableProvider): TypeDecorator; } & InjectableProvider): TypeDecorator;
new (): Injectable; new (): Injectable;
new (options?: { new (options?: {
providedIn: Type<any> | 'root' | null; providedIn: Type<any> | 'root' | 'platform' | 'any' | null;
} & InjectableProvider): Injectable; } & InjectableProvider): Injectable;
} }
@ -449,7 +449,7 @@ export declare class InjectionToken<T> {
protected _desc: string; protected _desc: string;
readonly ngInjectableDef: never | undefined; readonly ngInjectableDef: never | undefined;
constructor(_desc: string, options?: { constructor(_desc: string, options?: {
providedIn?: Type<any> | 'root' | null; providedIn?: Type<any> | 'root' | 'platform' | 'any' | null;
factory: () => T; factory: () => T;
}); });
toString(): string; toString(): string;
@ -811,7 +811,7 @@ export declare const ɵɵdefineDirective: <T>(directiveDefinition: {
export declare function ɵɵdefineInjectable<T>(opts: { export declare function ɵɵdefineInjectable<T>(opts: {
token: unknown; token: unknown;
providedIn?: Type<any> | 'root' | 'any' | null; providedIn?: Type<any> | 'root' | 'platform' | 'any' | null;
factory: () => T; factory: () => T;
}): never; }): never;
@ -906,7 +906,7 @@ export declare function ɵɵinject<T>(token: Type<T> | InjectionToken<T>, flags?
export interface ɵɵInjectableDef<T> { export interface ɵɵInjectableDef<T> {
factory: (t?: Type<any>) => T; factory: (t?: Type<any>) => T;
providedIn: InjectorType<any> | 'root' | 'any' | null; providedIn: InjectorType<any> | 'root' | 'platform' | 'any' | null;
token: unknown; token: unknown;
value: T | undefined; value: T | undefined;
} }