From 77c382ccbabf74c7b1d2cb9e3d18b8574abb91fc Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 22 Aug 2019 19:19:41 -0700 Subject: [PATCH] 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 --- packages/core/src/core_private_export.ts | 2 +- packages/core/src/di/injectable.ts | 22 +++++---- packages/core/src/di/injection_token.ts | 2 +- packages/core/src/di/injector.ts | 48 +++++++++++++++---- packages/core/src/di/interface/defs.ts | 4 +- packages/core/src/di/r3_injector.ts | 21 ++++---- packages/core/src/di/scope.ts | 3 +- packages/core/src/view/entrypoint.ts | 2 +- packages/core/src/view/ng_module.ts | 15 +++--- packages/core/src/view/types.ts | 2 +- packages/core/test/acceptance/di_spec.ts | 32 +++++++++++++ .../injection/bundle.golden_symbols.json | 2 +- packages/core/test/view/ng_module_spec.ts | 27 ++++++----- packages/core/testing/src/test_bed.ts | 4 +- packages/platform-browser/src/browser.ts | 4 +- tools/public_api_guard/core/core.d.ts | 12 ++--- 16 files changed, 138 insertions(+), 64 deletions(-) diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index f3978a1cbe40e..0498b825cc08f 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -15,7 +15,7 @@ export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetection export {Console as ɵConsole} from './console'; export {inject, setCurrentInjector as ɵsetCurrentInjector, ɵɵinject} from './di/injector_compatibility'; 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 {ivyEnabled as ɵivyEnabled} from './ivy_switch'; export {ComponentFactory as ɵComponentFactory} from './linker/component_factory'; diff --git a/packages/core/src/di/injectable.ts b/packages/core/src/di/injectable.ts index c76ae56a854ca..0eb983ae97214 100644 --- a/packages/core/src/di/injectable.ts +++ b/packages/core/src/di/injectable.ts @@ -50,9 +50,11 @@ export interface InjectableDecorator { * */ (): TypeDecorator; - (options?: {providedIn: Type| 'root' | null}&InjectableProvider): TypeDecorator; + (options?: {providedIn: Type| 'root' | 'platform' | 'any' | null}& + InjectableProvider): TypeDecorator; new (): Injectable; - new (options?: {providedIn: Type| 'root' | null}&InjectableProvider): Injectable; + new (options?: {providedIn: Type| '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|'root'|null; + providedIn?: Type|'root'|'platform'|'any'|null; } /** @@ -90,9 +96,9 @@ export interface InjectableType extends Type { ngInjectableDef: ɵɵInject /** * Supports @Injectable() in JIT mode for Render2. */ -function render2CompileInjectable( - injectableType: Type, - options?: {providedIn?: Type| 'root' | null} & InjectableProvider): void { +function render2CompileInjectable(injectableType: Type, options?: { + providedIn?: Type| 'root' | 'platform' | 'any' | null +} & InjectableProvider): void { if (options && options.providedIn !== undefined && !getInjectableDef(injectableType)) { (injectableType as InjectableType).ngInjectableDef = ɵɵdefineInjectable({ token: injectableType, diff --git a/packages/core/src/di/injection_token.ts b/packages/core/src/di/injection_token.ts index f93c31f36e582..7552fc7e539ae 100644 --- a/packages/core/src/di/injection_token.ts +++ b/packages/core/src/di/injection_token.ts @@ -57,7 +57,7 @@ export class InjectionToken { readonly ngInjectableDef: never|undefined; constructor(protected _desc: string, options?: { - providedIn?: Type| 'root' | null, + providedIn?: Type| 'root' | 'platform' | 'any' | null, factory: () => T }) { this.ngInjectableDef = undefined; diff --git a/packages/core/src/di/injector.ts b/packages/core/src/di/injector.ts index df9023dac8cf5..02ecfd46e6f08 100644 --- a/packages/core/src/di/injector.ts +++ b/packages/core/src/di/injector.ts @@ -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; + private _records: Map; constructor( providers: StaticProvider[], parent: Injector = Injector.NULL, source: string|null = null) { @@ -136,17 +138,37 @@ export class StaticInjector implements Injector { Injector, {token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false}); records.set( INJECTOR, {token: INJECTOR, fn: IDENT, deps: EMPTY, value: this, useNew: false}); - recursivelyProcessProviders(records, providers); + this.scope = recursivelyProcessProviders(records, providers); } get(token: Type|InjectionToken, 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, provider: StaticProvider) { +function recursivelyProcessProviders(records: Map, 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, 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, parent: Injector, + token: any, record: Record | undefined | null, records: Map, 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, parent: Injector, + token: any, record: Record | undefined | null, records: Map, parent: Injector, notFoundValue: any, flags: InjectFlags): any { let value; if (record && !(flags & InjectFlags.SkipSelf)) { diff --git a/packages/core/src/di/interface/defs.ts b/packages/core/src/di/interface/defs.ts index f6c3b935a2b56..aa793b9e9cddf 100644 --- a/packages/core/src/di/interface/defs.ts +++ b/packages/core/src/di/interface/defs.ts @@ -35,7 +35,7 @@ export interface ɵɵInjectableDef { * - `null`, does not belong to any injector. Must be explicitly listed in the injector * `providers`. */ - providedIn: InjectorType|'root'|'any'|null; + providedIn: InjectorType|'root'|'platform'|'any'|null; /** * The token to which this definition belongs. @@ -140,7 +140,7 @@ export interface InjectorTypeWithProviders { */ export function ɵɵdefineInjectable(opts: { token: unknown, - providedIn?: Type| 'root' | 'any' | null, + providedIn?: Type| 'root' | 'platform' | 'any' | null, factory: () => T, }): never { return ({ diff --git a/packages/core/src/di/r3_injector.ts b/packages/core/src/di/r3_injector.ts index 0629d0bc5f5e4..c92f3b739a0f4 100644 --- a/packages/core/src/di/r3_injector.ts +++ b/packages/core/src/di/r3_injector.ts @@ -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|InjectionToken, Record>(); + private records = new Map|InjectionToken, Record|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|undefined = this.records.get(token); + let record: Record|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); } diff --git a/packages/core/src/di/scope.ts b/packages/core/src/di/scope.ts index 6c9c13bc6a51b..229237a279b85 100644 --- a/packages/core/src/di/scope.ts +++ b/packages/core/src/di/scope.ts @@ -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( - '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.'); diff --git a/packages/core/src/view/entrypoint.ts b/packages/core/src/view/entrypoint.ts index 7786cae734591..ae1cb7004eb7d 100644 --- a/packages/core/src/view/entrypoint.ts +++ b/packages/core/src/view/entrypoint.ts @@ -48,7 +48,7 @@ function cloneNgModuleDefinition(def: NgModuleDefinition): NgModuleDefinition { return { factory: def.factory, - isRoot: def.isRoot, providers, modules, providersByKey, + scope: def.scope, providers, modules, providersByKey, }; } diff --git a/packages/core/src/view/ng_module.ts b/packages/core/src/view/ng_module.ts index 1c5626ce8180c..e0d5d17458c04 100644 --- a/packages/core/src/view/ng_module.ts +++ b/packages/core/src/view/ng_module.ts @@ -10,7 +10,7 @@ import {resolveForwardRef} from '../di/forward_ref'; import {Injector} from '../di/injector'; import {INJECTOR, setCurrentInjector} from '../di/injector_compatibility'; 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 {newArray} from '../util/array_utils'; import {stringify} from '../util/stringify'; @@ -42,11 +42,11 @@ export function moduleProvideDef( export function moduleDef(providers: NgModuleProviderDef[]): NgModuleDefinition { const providersByKey: {[key: string]: NgModuleProviderDef} = {}; const modules = []; - let isRoot: boolean = false; + let scope: 'root'|'platform'|null = null; for (let i = 0; i < providers.length; i++) { const provider = providers[i]; - if (provider.token === APP_ROOT && provider.value === true) { - isRoot = true; + if (provider.token === INJECTOR_SCOPE) { + scope = provider.value; } if (provider.flags & NodeFlags.TypeNgModule) { modules.push(provider.token); @@ -60,7 +60,7 @@ export function moduleDef(providers: NgModuleProviderDef[]): NgModuleDefinition providersByKey, providers, modules, - isRoot, + scope: scope, }; } @@ -134,8 +134,9 @@ function moduleTransitivelyPresent(ngModule: NgModuleData, scope: any): boolean } function targetsModule(ngModule: NgModuleData, def: ɵɵInjectableDef): boolean { - return def.providedIn != null && (moduleTransitivelyPresent(ngModule, def.providedIn) || - def.providedIn === 'root' && ngModule._def.isRoot); + const providedIn = def.providedIn; + return providedIn != null && (providedIn === 'any' || providedIn === ngModule._def.scope || + moduleTransitivelyPresent(ngModule, providedIn)); } function _createProviderInstance(ngModule: NgModuleData, providerDef: NgModuleProviderDef): any { diff --git a/packages/core/src/view/types.ts b/packages/core/src/view/types.ts index 004636d66a63b..3172afa33bc79 100644 --- a/packages/core/src/view/types.ts +++ b/packages/core/src/view/types.ts @@ -45,7 +45,7 @@ export interface NgModuleDefinition extends Definition {} diff --git a/packages/core/test/acceptance/di_spec.ts b/packages/core/test/acceptance/di_spec.ts index d32dcbc68f798..cb4aba86f8a87 100644 --- a/packages/core/test/acceptance/di_spec.ts +++ b/packages/core/test/acceptance/di_spec.ts @@ -8,6 +8,7 @@ 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 {ɵINJECTOR_SCOPE} from '@angular/core/src/core'; import {ViewRef} from '@angular/core/src/render3/view_ref'; import {TestBed} from '@angular/core/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', () => { it('should create instance even when no injector present', () => { diff --git a/packages/core/test/bundling/injection/bundle.golden_symbols.json b/packages/core/test/bundling/injection/bundle.golden_symbols.json index 9898bc02bb8d2..398fa73511751 100644 --- a/packages/core/test/bundling/injection/bundle.golden_symbols.json +++ b/packages/core/test/bundling/injection/bundle.golden_symbols.json @@ -1,6 +1,6 @@ [ { - "name": "APP_ROOT" + "name": "INJECTOR_SCOPE" }, { "name": "CIRCULAR" diff --git a/packages/core/test/view/ng_module_spec.ts b/packages/core/test/view/ng_module_spec.ts index f6b75a8920003..a81f9188a7fbd 100644 --- a/packages/core/test/view/ng_module_spec.ts +++ b/packages/core/test/view/ng_module_spec.ts @@ -6,7 +6,7 @@ * 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 {Injector} from '@angular/core/src/di/injector'; 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 {tokenKey} from '@angular/core/src/view/util'; -import {APP_ROOT} from '../../src/di/scope'; - class Foo {} class MyModule {} @@ -133,7 +131,7 @@ function makeFactoryProviders( function makeModule(modules: any[], providers: NgModuleProviderDef[]): NgModuleDefinition { const providersByKey: {[key: string]: NgModuleProviderDef} = {}; 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', () => { @@ -273,19 +271,24 @@ describe('NgModuleRef_ injector', () => { }; } - it('sets isRoot to `true` when APP_ROOT is `true`', () => { - const def = moduleDef([createProvider(APP_ROOT, true)]); - expect(def.isRoot).toBe(true); + it('sets scope to `root` when INJECTOR_SCOPE is `root`', () => { + const def = moduleDef([createProvider(INJECTOR_SCOPE, 'root')]); + expect(def.scope).toBe('root'); + }); + + it('sets scope to `platform` when INJECTOR_SCOPE is `platform`', () => { + const def = moduleDef([createProvider(INJECTOR_SCOPE, 'platform')]); + expect(def.scope).toBe('platform'); }); - it('sets isRoot to `false` when APP_ROOT is absent', () => { + it('sets scope to `null` when INJECTOR_SCOPE is absent', () => { const def = moduleDef([]); - expect(def.isRoot).toBe(false); + expect(def.scope).toBe(null); }); - it('sets isRoot to `false` when APP_ROOT is `false`', () => { - const def = moduleDef([createProvider(APP_ROOT, false)]); - expect(def.isRoot).toBe(false); + it('sets isRoot to `null` when INJECTOR_SCOPE is `null`', () => { + const def = moduleDef([createProvider(INJECTOR_SCOPE, null)]); + expect(def.scope).toBe(null); }); }); }); diff --git a/packages/core/testing/src/test_bed.ts b/packages/core/testing/src/test_bed.ts index 9f8692d83697c..6ee828b8e5f87 100644 --- a/packages/core/testing/src/test_bed.ts +++ b/packages/core/testing/src/test_bed.ts @@ -6,7 +6,7 @@ * 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 {ComponentFixture} from './component_fixture'; @@ -434,7 +434,7 @@ export class TestBedViewEngine implements TestBed { } 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 schemas = this._schemas; diff --git a/packages/platform-browser/src/browser.ts b/packages/platform-browser/src/browser.ts index bcfdeac92ceae..c35e32d4851bb 100644 --- a/packages/platform-browser/src/browser.ts +++ b/packages/platform-browser/src/browser.ts @@ -7,7 +7,7 @@ */ 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 {BrowserPlatformLocation} from './browser/location/browser_platform_location'; @@ -65,7 +65,7 @@ export function _document(): any { export const BROWSER_MODULE_PROVIDERS: StaticProvider[] = [ BROWSER_SANITIZATION_PROVIDERS, - {provide: APP_ROOT, useValue: true}, + {provide: INJECTOR_SCOPE, useValue: 'root'}, {provide: ErrorHandler, useFactory: errorHandler, deps: []}, { provide: EVENT_MANAGER_PLUGINS, diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index c9ffd6b0bdf73..9cb885bd1dcc9 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -410,7 +410,7 @@ export interface Inject { export declare const Inject: InjectDecorator; export interface Injectable { - providedIn?: Type | 'root' | null; + providedIn?: Type | 'root' | 'platform' | 'any' | null; } export declare const Injectable: InjectableDecorator; @@ -418,11 +418,11 @@ export declare const Injectable: InjectableDecorator; export interface InjectableDecorator { (): TypeDecorator; (options?: { - providedIn: Type | 'root' | null; + providedIn: Type | 'root' | 'platform' | 'any' | null; } & InjectableProvider): TypeDecorator; new (): Injectable; new (options?: { - providedIn: Type | 'root' | null; + providedIn: Type | 'root' | 'platform' | 'any' | null; } & InjectableProvider): Injectable; } @@ -449,7 +449,7 @@ export declare class InjectionToken { protected _desc: string; readonly ngInjectableDef: never | undefined; constructor(_desc: string, options?: { - providedIn?: Type | 'root' | null; + providedIn?: Type | 'root' | 'platform' | 'any' | null; factory: () => T; }); toString(): string; @@ -811,7 +811,7 @@ export declare const ɵɵdefineDirective: (directiveDefinition: { export declare function ɵɵdefineInjectable(opts: { token: unknown; - providedIn?: Type | 'root' | 'any' | null; + providedIn?: Type | 'root' | 'platform' | 'any' | null; factory: () => T; }): never; @@ -906,7 +906,7 @@ export declare function ɵɵinject(token: Type | InjectionToken, flags? export interface ɵɵInjectableDef { factory: (t?: Type) => T; - providedIn: InjectorType | 'root' | 'any' | null; + providedIn: InjectorType | 'root' | 'platform' | 'any' | null; token: unknown; value: T | undefined; }