feat: tree-shakeable providers API updates (#22655)

Rename @Injectable({scope -> providedIn}).

Instead of {providedIn: APP_ROOT_SCOPE}, accept {providedIn: 'root'}.
Also, {providedIn: null} implies the injectable should not be added
to any scope.

PR Close #22655
This commit is contained in:
Alex Rickabaugh
2018-03-07 15:10:38 -08:00
committed by Kara Erickson
parent 21e44c6ba9
commit db56836425
29 changed files with 219 additions and 114 deletions

View File

@ -13,6 +13,7 @@ export {isListLikeIterable as ɵisListLikeIterable} from './change_detection/cha
export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetectionStrategy as ɵisDefaultChangeDetectionStrategy} from './change_detection/constants';
export {Console as ɵConsole} from './console';
export {setCurrentInjector as ɵsetCurrentInjector} from './di/injector';
export {APP_ROOT as ɵAPP_ROOT} from './di/scope';
export {ComponentFactory as ɵComponentFactory} from './linker/component_factory';
export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} from './linker/component_factory_resolver';
export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities';

View File

@ -23,4 +23,3 @@ export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provid
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
export {ReflectiveKey} from './di/reflective_key';
export {InjectionToken} from './di/injection_token';
export {APP_ROOT_SCOPE} from './di/scope';

View File

@ -55,9 +55,9 @@ export interface InjectableDecorator {
* @stable
*/
(): any;
(options?: {scope: Type<any>}&InjectableProvider): any;
(options?: {providedIn: Type<any>| 'root' | null}&InjectableProvider): any;
new (): Injectable;
new (options?: {scope: Type<any>}&InjectableProvider): Injectable;
new (options?: {providedIn: Type<any>| 'root' | null}&InjectableProvider): Injectable;
}
/**
@ -66,7 +66,7 @@ export interface InjectableDecorator {
* @experimental
*/
export interface Injectable {
scope?: Type<any>;
providedIn?: Type<any>|'root'|null;
factory: () => any;
}
@ -109,12 +109,19 @@ export function convertInjectableProviderToFactory(
}
/**
* Define injectable
* Construct an `InjectableDef` which defines how a token will be constructed by the DI system, and
* in which injectors (if any) it will be available.
*
* @experimental
*/
export function defineInjectable(opts: Injectable): Injectable {
return opts;
export function defineInjectable<T>(opts: {
providedIn?: Type<any>| 'root' | null,
factory: () => T,
}): InjectableDef<T> {
return {
providedIn: opts.providedIn || null,
factory: opts.factory,
};
}
/**
@ -125,19 +132,24 @@ export function defineInjectable(opts: Injectable): Injectable {
*/
export const Injectable: InjectableDecorator = makeDecorator(
'Injectable', undefined, undefined, undefined,
(injectableType: Type<any>, options: {scope: Type<any>} & InjectableProvider) => {
if (options && options.scope) {
(injectableType: Type<any>,
options: {providedIn?: Type<any>| 'root' | null} & InjectableProvider) => {
if (options && options.providedIn) {
(injectableType as InjectableType<any>).ngInjectableDef = defineInjectable({
scope: options.scope,
providedIn: options.providedIn,
factory: convertInjectableProviderToFactory(injectableType, options)
});
}
});
export interface InjectableDef<T> {
providedIn: Type<any>|'root'|null;
factory: () => T;
}
/**
* Type representing injectable service.
*
* @experimental
*/
export interface InjectableType<T> extends Type<T> { ngInjectableDef?: Injectable; }
export interface InjectableType<T> extends Type<T> { ngInjectableDef: InjectableDef<T>; }

View File

@ -8,7 +8,7 @@
import {Type} from '../type';
import {Injectable, defineInjectable} from './injectable';
import {InjectableDef, defineInjectable} from './injectable';
/**
* Creates a token that can be used in a DI Provider.
@ -36,12 +36,15 @@ export class InjectionToken<T> {
/** @internal */
readonly ngMetadataName = 'InjectionToken';
readonly ngInjectableDef: Injectable|undefined;
readonly ngInjectableDef: InjectableDef<T>|undefined;
constructor(protected _desc: string, options?: {scope: Type<any>, factory: () => T}) {
constructor(protected _desc: string, options?: {
providedIn?: Type<any>| 'root' | null,
factory: () => T
}) {
if (options !== undefined) {
this.ngInjectableDef = defineInjectable({
scope: options.scope,
providedIn: options.providedIn || 'root',
factory: options.factory,
});
} else {

View File

@ -10,18 +10,10 @@ import {Type} from '../type';
import {InjectionToken} from './injection_token';
// APP_ROOT_SCOPE is cast as a Type to allow for its usage as the scope parameter of @Injectable().
/**
* A scope which targets the root injector.
*
* When specified as the `scope` parameter to `@Injectable` or `InjectionToken`, this special
* scope indicates the provider for the service or token being configured belongs in the root
* injector. This is loosely equivalent to the convention of having a `forRoot()` static
* function within a module that configures the provider, and expecting users to only import that
* module via its `forRoot()` function in the root injector.
*
* @experimental
* 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 APP_ROOT_SCOPE: Type<any> = new InjectionToken<boolean>(
'The presence of this token marks an injector as being the root injector.') as any;
export const APP_ROOT = new InjectionToken<boolean>(
'The presence of this token marks an injector as being the root injector.');

View File

@ -7,12 +7,13 @@
*/
import {resolveForwardRef} from '../di/forward_ref';
import {InjectableDef} from '../di/injectable';
import {InjectFlags, Injector, setCurrentInjector} from '../di/injector';
import {APP_ROOT_SCOPE} from '../di/scope';
import {APP_ROOT} from '../di/scope';
import {NgModuleRef} from '../linker/ng_module_factory';
import {stringify} from '../util';
import {DepDef, DepFlags, InjectableDef, NgModuleData, NgModuleDefinition, NgModuleProviderDef, NodeFlags} from './types';
import {DepDef, DepFlags, NgModuleData, NgModuleDefinition, NgModuleProviderDef, NodeFlags} from './types';
import {splitDepsDsl, tokenKey} from './util';
const UNDEFINED_VALUE = new Object();
@ -20,12 +21,6 @@ const UNDEFINED_VALUE = new Object();
const InjectorRefTokenKey = tokenKey(Injector);
const NgModuleRefTokenKey = tokenKey(NgModuleRef);
export function injectableDef(scope: any, factory: () => any): InjectableDef {
return {
scope, factory,
};
}
export function moduleProvideDef(
flags: NodeFlags, token: any, value: any,
deps: ([DepFlags, any] | any)[]): NgModuleProviderDef {
@ -47,7 +42,7 @@ export function moduleDef(providers: NgModuleProviderDef[]): NgModuleDefinition
let isRoot: boolean = false;
for (let i = 0; i < providers.length; i++) {
const provider = providers[i];
if (provider.token === APP_ROOT_SCOPE) {
if (provider.token === APP_ROOT) {
isRoot = true;
}
if (provider.flags & NodeFlags.TypeNgModule) {
@ -103,7 +98,7 @@ export function resolveNgModuleDep(
}
return providerInstance === UNDEFINED_VALUE ? undefined : providerInstance;
} else if (depDef.token.ngInjectableDef && targetsModule(data, depDef.token.ngInjectableDef)) {
const injectableDef = depDef.token.ngInjectableDef as InjectableDef;
const injectableDef = depDef.token.ngInjectableDef as InjectableDef<any>;
const key = tokenKey;
const index = data._providers.length;
data._def.providersByKey[depDef.tokenKey] = {
@ -129,9 +124,9 @@ function moduleTransitivelyPresent(ngModule: NgModuleData, scope: any): boolean
return ngModule._def.modules.indexOf(scope) > -1;
}
function targetsModule(ngModule: NgModuleData, def: InjectableDef): boolean {
return def.scope != null && (moduleTransitivelyPresent(ngModule, def.scope) ||
def.scope === APP_ROOT_SCOPE && ngModule._def.isRoot);
function targetsModule(ngModule: NgModuleData, def: InjectableDef<any>): boolean {
return def.providedIn != null && (moduleTransitivelyPresent(ngModule, def.providedIn) ||
def.providedIn === 'root' && ngModule._def.isRoot);
}
function _createProviderInstance(ngModule: NgModuleData, providerDef: NgModuleProviderDef): any {

View File

@ -8,13 +8,14 @@
import {isDevMode} from '../application_ref';
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node';
import {Injector} from '../di';
import {InjectableType, Injector} from '../di';
import {ErrorHandler} from '../error_handler';
import {ComponentFactory} from '../linker/component_factory';
import {NgModuleRef} from '../linker/ng_module_factory';
import {Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '../render/api';
import {Sanitizer} from '../sanitization/security';
import {Type} from '../type';
import {tokenKey} from '../view/util';
import {isViewDebugError, viewDestroyedError, viewWrappedDebugError} from './errors';
import {resolveDep} from './provider';
@ -162,10 +163,15 @@ function debugCreateNgModuleRef(
}
const providerOverrides = new Map<any, ProviderOverride>();
const providerOverridesWithScope = new Map<InjectableType<any>, ProviderOverride>();
const viewDefOverrides = new Map<any, ViewDefinition>();
function debugOverrideProvider(override: ProviderOverride) {
providerOverrides.set(override.token, override);
if (typeof override.token === 'function' && override.token.ngInjectableDef &&
typeof override.token.ngInjectableDef.providedIn === 'function') {
providerOverridesWithScope.set(override.token as InjectableType<any>, override);
}
}
function debugOverrideComponentView(comp: any, compFactory: ComponentFactory<any>) {
@ -176,6 +182,7 @@ function debugOverrideComponentView(comp: any, compFactory: ComponentFactory<any
function debugClearOverrides() {
providerOverrides.clear();
providerOverridesWithScope.clear();
viewDefOverrides.clear();
}
@ -266,6 +273,14 @@ function applyProviderOverridesToNgModule(def: NgModuleDefinition): NgModuleDefi
hasDeprecatedOverrides = hasDeprecatedOverrides || override.deprecatedBehavior;
}
});
def.modules.forEach(module => {
providerOverridesWithScope.forEach((override, token) => {
if (token.ngInjectableDef.providedIn === module) {
hasOverrides = true;
hasDeprecatedOverrides = hasDeprecatedOverrides || override.deprecatedBehavior;
}
});
});
return {hasOverrides, hasDeprecatedOverrides};
}
@ -285,6 +300,23 @@ function applyProviderOverridesToNgModule(def: NgModuleDefinition): NgModuleDefi
provider.value = override.value;
}
}
if (providerOverridesWithScope.size > 0) {
let moduleSet = new Set<any>(def.modules);
providerOverridesWithScope.forEach((override, token) => {
if (moduleSet.has(token.ngInjectableDef.providedIn)) {
let provider = {
token: token,
flags:
override.flags | (hasDeprecatedOverrides ? NodeFlags.LazyProvider : NodeFlags.None),
deps: splitDepsDsl(override.deps),
value: override.value,
index: def.providers.length,
};
def.providers.push(provider);
def.providersByKey[tokenKey(token)] = provider;
}
});
}
}
}

View File

@ -297,11 +297,6 @@ export const enum DepFlags {
Value = 1 << 3,
}
export interface InjectableDef {
scope: any;
factory: () => any;
}
export interface TextDef { prefix: string; }
export interface QueryDef {