feat: change @Injectable() to support tree-shakeable tokens (#22005)
This commit bundles 3 important changes, with the goal of enabling tree-shaking of services which are never injected. Ordinarily, this tree-shaking is prevented by the existence of a hard dependency on the service by the module in which it is declared. Firstly, @Injectable() is modified to accept a 'scope' parameter, which points to an @NgModule(). This reverses the dependency edge, permitting the module to not depend on the service which it "provides". Secondly, the runtime is modified to understand the new relationship created above. When a module receives a request to inject a token, and cannot find that token in its list of providers, it will then look at the token for a special ngInjectableDef field which indicates which module the token is scoped to. If that module happens to be in the injector, it will behave as if the token itself was in the injector to begin with. Thirdly, the compiler is modified to read the @Injectable() metadata and to generate the special ngInjectableDef field as part of TS compilation, using the PartialModules system. Additionally, this commit adds several unit and integration tests of various flavors to test this change. PR Close #22005
This commit is contained in:

committed by
Miško Hevery

parent
2d5e7d1b52
commit
235a235fab
@ -6,7 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Optional, SkipSelf, StaticProvider} from '../../di';
|
||||
import {Optional, SkipSelf} from '../../di/metadata';
|
||||
import {StaticProvider} from '../../di/provider';
|
||||
|
||||
|
||||
/**
|
||||
|
@ -13,10 +13,11 @@
|
||||
*/
|
||||
|
||||
export * from './di/metadata';
|
||||
export {defineInjectable, Injectable, InjectableDecorator, InjectableProvider, InjectableType} from './di/injectable';
|
||||
|
||||
export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref';
|
||||
|
||||
export {Injector} from './di/injector';
|
||||
export {InjectFlags, Injector} from './di/injector';
|
||||
export {ReflectiveInjector} from './di/reflective_injector';
|
||||
export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider';
|
||||
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
|
||||
|
143
packages/core/src/di/injectable.ts
Normal file
143
packages/core/src/di/injectable.ts
Normal file
@ -0,0 +1,143 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ReflectionCapabilities} from '../reflection/reflection_capabilities';
|
||||
import {Type} from '../type';
|
||||
import {makeDecorator, makeParamDecorator} from '../util/decorators';
|
||||
import {getClosureSafeProperty} from '../util/property';
|
||||
|
||||
import {inject, injectArgs} from './injector';
|
||||
import {ClassSansProvider, ConstructorProvider, ConstructorSansProvider, ExistingProvider, ExistingSansProvider, FactoryProvider, FactorySansProvider, StaticClassProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from './provider';
|
||||
|
||||
const GET_PROPERTY_NAME = {} as any;
|
||||
const USE_VALUE = getClosureSafeProperty<ValueProvider>(
|
||||
{provide: String, useValue: GET_PROPERTY_NAME}, GET_PROPERTY_NAME);
|
||||
|
||||
/**
|
||||
* Injectable providers used in `@Injectable` decorator.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export type InjectableProvider = ValueSansProvider | ExistingSansProvider |
|
||||
StaticClassSansProvider | ConstructorSansProvider | FactorySansProvider | ClassSansProvider;
|
||||
|
||||
/**
|
||||
* Type of the Injectable decorator / constructor function.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface InjectableDecorator {
|
||||
/**
|
||||
* @whatItDoes A marker metadata that marks a class as available to {@link Injector} for creation.
|
||||
* @howToUse
|
||||
* ```
|
||||
* @Injectable()
|
||||
* class Car {}
|
||||
* ```
|
||||
*
|
||||
* @description
|
||||
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/di/ts/metadata_spec.ts region='Injectable'}
|
||||
*
|
||||
* {@link Injector} will throw an error when trying to instantiate a class that
|
||||
* does not have `@Injectable` marker, as shown in the example below.
|
||||
*
|
||||
* {@example core/di/ts/metadata_spec.ts region='InjectableThrows'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
(): any;
|
||||
(options?: {scope: Type<any>}&InjectableProvider): any;
|
||||
new (): Injectable;
|
||||
new (options?: {scope: Type<any>}&InjectableProvider): Injectable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of the Injectable metadata.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface Injectable {
|
||||
scope?: Type<any>;
|
||||
factory: () => any;
|
||||
}
|
||||
|
||||
const EMPTY_ARRAY: any[] = [];
|
||||
|
||||
export function convertInjectableProviderToFactory(
|
||||
type: Type<any>, provider?: InjectableProvider): () => any {
|
||||
if (!provider) {
|
||||
const reflectionCapabilities = new ReflectionCapabilities();
|
||||
const deps = reflectionCapabilities.parameters(type);
|
||||
// TODO - convert to flags.
|
||||
return () => new type(...injectArgs(deps as any[]));
|
||||
}
|
||||
|
||||
if (USE_VALUE in provider) {
|
||||
const valueProvider = (provider as ValueSansProvider);
|
||||
return () => valueProvider.useValue;
|
||||
} else if ((provider as ExistingSansProvider).useExisting) {
|
||||
const existingProvider = (provider as ExistingSansProvider);
|
||||
return () => inject(existingProvider.useExisting);
|
||||
} else if ((provider as FactorySansProvider).useFactory) {
|
||||
const factoryProvider = (provider as FactorySansProvider);
|
||||
return () => factoryProvider.useFactory(...injectArgs(factoryProvider.deps || EMPTY_ARRAY));
|
||||
} else if ((provider as StaticClassSansProvider | ClassSansProvider).useClass) {
|
||||
const classProvider = (provider as StaticClassSansProvider | ClassSansProvider);
|
||||
let deps = (provider as StaticClassSansProvider).deps;
|
||||
if (!deps) {
|
||||
const reflectionCapabilities = new ReflectionCapabilities();
|
||||
deps = reflectionCapabilities.parameters(type);
|
||||
}
|
||||
return () => new classProvider.useClass(...injectArgs(deps));
|
||||
} else {
|
||||
let deps = (provider as ConstructorSansProvider).deps;
|
||||
if (!deps) {
|
||||
const reflectionCapabilities = new ReflectionCapabilities();
|
||||
deps = reflectionCapabilities.parameters(type);
|
||||
}
|
||||
return () => new type(...injectArgs(deps !));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define injectable
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export function defineInjectable(opts: Injectable): Injectable {
|
||||
return opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injectable decorator and metadata.
|
||||
*
|
||||
* @stable
|
||||
* @Annotation
|
||||
*/
|
||||
export const Injectable: InjectableDecorator = makeDecorator(
|
||||
'Injectable', undefined, undefined, undefined,
|
||||
(injectableType: Type<any>, options: {scope: Type<any>} & InjectableProvider) => {
|
||||
if (options && options.scope) {
|
||||
(injectableType as InjectableType<any>).ngInjectableDef = defineInjectable({
|
||||
scope: options.scope,
|
||||
factory: convertInjectableProviderToFactory(injectableType, options)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Type representing injectable service.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface InjectableType<T> extends Type<T> { ngInjectableDef?: Injectable; }
|
@ -6,6 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '../type';
|
||||
|
||||
import {Injectable, convertInjectableProviderToFactory, defineInjectable} from './injectable';
|
||||
import {ClassSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueSansProvider} from './provider';
|
||||
|
||||
export type InjectionTokenProvider = ValueSansProvider | ExistingSansProvider |
|
||||
FactorySansProvider | ClassSansProvider | StaticClassSansProvider;
|
||||
|
||||
/**
|
||||
* Creates a token that can be used in a DI Provider.
|
||||
*
|
||||
@ -32,7 +40,18 @@ export class InjectionToken<T> {
|
||||
/** @internal */
|
||||
readonly ngMetadataName = 'InjectionToken';
|
||||
|
||||
constructor(protected _desc: string) {}
|
||||
readonly ngInjectableDef: Injectable|undefined;
|
||||
|
||||
constructor(protected _desc: string, options?: {scope: Type<any>}&InjectionTokenProvider) {
|
||||
if (options !== undefined) {
|
||||
this.ngInjectableDef = defineInjectable({
|
||||
scope: options.scope,
|
||||
factory: convertInjectableProviderToFactory(this as any, options),
|
||||
});
|
||||
} else {
|
||||
this.ngInjectableDef = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
toString(): string { return `InjectionToken ${this._desc}`; }
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ export abstract class Injector {
|
||||
* Injector.THROW_IF_NOT_FOUND is given
|
||||
* - Returns the `notFoundValue` otherwise
|
||||
*/
|
||||
abstract get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T): T;
|
||||
abstract get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
|
||||
/**
|
||||
* @deprecated from v4.0.0 use Type<T> or InjectionToken<T>
|
||||
* @suppress {duplicate}
|
||||
@ -130,12 +130,12 @@ export class StaticInjector implements Injector {
|
||||
recursivelyProcessProviders(records, providers);
|
||||
}
|
||||
|
||||
get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T): 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 {
|
||||
const record = this._records.get(token);
|
||||
try {
|
||||
return tryResolveToken(token, record, this._records, this.parent, notFoundValue);
|
||||
return tryResolveToken(token, record, this._records, this.parent, notFoundValue, flags);
|
||||
} catch (e) {
|
||||
const tokenPath: any[] = e[NG_TEMP_TOKEN_PATH];
|
||||
if (token[SOURCE]) {
|
||||
@ -253,9 +253,9 @@ function recursivelyProcessProviders(records: Map<any, Record>, provider: Static
|
||||
|
||||
function tryResolveToken(
|
||||
token: any, record: Record | undefined, records: Map<any, Record>, parent: Injector,
|
||||
notFoundValue: any): any {
|
||||
notFoundValue: any, flags: InjectFlags): any {
|
||||
try {
|
||||
return resolveToken(token, record, records, parent, notFoundValue);
|
||||
return resolveToken(token, record, records, parent, notFoundValue, flags);
|
||||
} catch (e) {
|
||||
// ensure that 'e' is of type Error.
|
||||
if (!(e instanceof Error)) {
|
||||
@ -273,9 +273,9 @@ function tryResolveToken(
|
||||
|
||||
function resolveToken(
|
||||
token: any, record: Record | undefined, records: Map<any, Record>, parent: Injector,
|
||||
notFoundValue: any): any {
|
||||
notFoundValue: any, flags: InjectFlags): any {
|
||||
let value;
|
||||
if (record) {
|
||||
if (record && !(flags & InjectFlags.SkipSelf)) {
|
||||
// If we don't have a record, this implies that we don't own the provider hence don't know how
|
||||
// to resolve it.
|
||||
value = record.value;
|
||||
@ -306,13 +306,14 @@ function resolveToken(
|
||||
// If we don't know how to resolve dependency and we should not check parent for it,
|
||||
// than pass in Null injector.
|
||||
!childRecord && !(options & OptionFlags.CheckParent) ? NULL_INJECTOR : parent,
|
||||
options & OptionFlags.Optional ? null : Injector.THROW_IF_NOT_FOUND));
|
||||
options & OptionFlags.Optional ? null : Injector.THROW_IF_NOT_FOUND,
|
||||
InjectFlags.Default));
|
||||
}
|
||||
}
|
||||
record.value = value = useNew ? new (fn as any)(...deps) : fn.apply(obj, deps);
|
||||
}
|
||||
} else {
|
||||
value = parent.get(token, notFoundValue);
|
||||
} else if (!(flags & InjectFlags.Self)) {
|
||||
value = parent.get(token, notFoundValue, InjectFlags.Default);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@ -386,3 +387,73 @@ function getClosureSafeProperty<T>(objWithPropertyToExtract: T): string {
|
||||
}
|
||||
throw Error('!prop');
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection flags for DI.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export const enum InjectFlags {
|
||||
Default = 0,
|
||||
|
||||
/** Skip the node that is requesting injection. */
|
||||
SkipSelf = 1 << 0,
|
||||
/** Don't descend into ancestors of the node requesting injection. */
|
||||
Self = 1 << 1,
|
||||
}
|
||||
|
||||
let _currentInjector: Injector|null = null;
|
||||
|
||||
export function setCurrentInjector(injector: Injector | null): Injector|null {
|
||||
const former = _currentInjector;
|
||||
_currentInjector = injector;
|
||||
return former;
|
||||
}
|
||||
|
||||
export function inject<T>(
|
||||
token: Type<T>| InjectionToken<T>, notFoundValue?: undefined, flags?: InjectFlags): T;
|
||||
export function inject<T>(
|
||||
token: Type<T>| InjectionToken<T>, notFoundValue: T | null, flags?: InjectFlags): T|null;
|
||||
export function inject<T>(
|
||||
token: Type<T>| InjectionToken<T>, notFoundValue?: T | null, flags = InjectFlags.Default): T|
|
||||
null {
|
||||
if (_currentInjector === null) {
|
||||
throw new Error(`inject() must be called from an injection context`);
|
||||
}
|
||||
return _currentInjector.get(token, notFoundValue, flags);
|
||||
}
|
||||
|
||||
export function injectArgs(types: (Type<any>| InjectionToken<any>| any[])[]): any[] {
|
||||
const args: any[] = [];
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
const arg = types[i];
|
||||
if (Array.isArray(arg)) {
|
||||
if (arg.length === 0) {
|
||||
throw new Error('Arguments array must have arguments.');
|
||||
}
|
||||
let type: Type<any>|undefined = undefined;
|
||||
let defaultValue: null|undefined = undefined;
|
||||
let flags: InjectFlags = InjectFlags.Default;
|
||||
|
||||
for (let j = 0; j < arg.length; j++) {
|
||||
const meta = arg[j];
|
||||
if (meta instanceof Optional || meta.__proto__.ngMetadataName === 'Optional') {
|
||||
defaultValue = null;
|
||||
} else if (meta instanceof SkipSelf || meta.__proto__.ngMetadataName === 'SkipSelf') {
|
||||
flags |= InjectFlags.SkipSelf;
|
||||
} else if (meta instanceof Self || meta.__proto__.ngMetadataName === 'Self') {
|
||||
flags |= InjectFlags.Self;
|
||||
} else if (meta instanceof Inject) {
|
||||
type = meta.token;
|
||||
} else {
|
||||
type = meta;
|
||||
}
|
||||
}
|
||||
|
||||
args.push(inject(type !, defaultValue, InjectFlags.Default));
|
||||
} else {
|
||||
args.push(inject(arg));
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
@ -6,7 +6,11 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ClassSansProvider, ConstructorProvider, ConstructorSansProvider, ExistingProvider, ExistingSansProvider, FactoryProvider, FactorySansProvider, StaticClassProvider, StaticClassSansProvider, ValueProvider, ValueSansProvider} from '../di/provider';
|
||||
import {ReflectionCapabilities} from '../reflection/reflection_capabilities';
|
||||
import {Type} from '../type';
|
||||
import {makeDecorator, makeParamDecorator} from '../util/decorators';
|
||||
import {EMPTY_ARRAY} from '../view/util';
|
||||
|
||||
|
||||
/**
|
||||
@ -106,53 +110,6 @@ export interface Optional {}
|
||||
*/
|
||||
export const Optional: OptionalDecorator = makeParamDecorator('Optional');
|
||||
|
||||
/**
|
||||
* Type of the Injectable decorator / constructor function.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface InjectableDecorator {
|
||||
/**
|
||||
* @whatItDoes A marker metadata that marks a class as available to {@link Injector} for creation.
|
||||
* @howToUse
|
||||
* ```
|
||||
* @Injectable()
|
||||
* class Car {}
|
||||
* ```
|
||||
*
|
||||
* @description
|
||||
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/di/ts/metadata_spec.ts region='Injectable'}
|
||||
*
|
||||
* {@link Injector} will throw an error when trying to instantiate a class that
|
||||
* does not have `@Injectable` marker, as shown in the example below.
|
||||
*
|
||||
* {@example core/di/ts/metadata_spec.ts region='InjectableThrows'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
(): any;
|
||||
new (): Injectable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of the Injectable metadata.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface Injectable {}
|
||||
|
||||
/**
|
||||
* Injectable decorator and metadata.
|
||||
*
|
||||
* @stable
|
||||
* @Annotation
|
||||
*/
|
||||
export const Injectable: InjectableDecorator = makeDecorator('Injectable');
|
||||
|
||||
/**
|
||||
* Type of the Self decorator / constructor function.
|
||||
*
|
||||
|
@ -8,6 +8,30 @@
|
||||
|
||||
import {Type} from '../type';
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the {@link Injector} to return a value for a token.
|
||||
* @howToUse
|
||||
* ```
|
||||
* @Injectable(SomeModule, {useValue: 'someValue'})
|
||||
* class SomeClass {}
|
||||
* ```
|
||||
*
|
||||
* @description
|
||||
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/di/ts/provider_spec.ts region='ValueSansProvider'}
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface ValueSansProvider {
|
||||
/**
|
||||
* The value to inject.
|
||||
*/
|
||||
useValue: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the {@link Injector} to return a value for a token.
|
||||
* @howToUse
|
||||
@ -24,17 +48,12 @@ import {Type} from '../type';
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface ValueProvider {
|
||||
export interface ValueProvider extends ValueSansProvider {
|
||||
/**
|
||||
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
|
||||
*/
|
||||
provide: any;
|
||||
|
||||
/**
|
||||
* The value to inject.
|
||||
*/
|
||||
useValue: any;
|
||||
|
||||
/**
|
||||
* If true, then injector returns an array of instances. This is useful to allow multiple
|
||||
* providers spread across many files to provide configuration information to a common token.
|
||||
@ -46,6 +65,37 @@ export interface ValueProvider {
|
||||
multi?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the {@link Injector} to return an instance of `useClass` for a token.
|
||||
* @howToUse
|
||||
* ```
|
||||
* @Injectable(SomeModule, {useClass: MyService, deps: []})
|
||||
* class MyService {}
|
||||
* ```
|
||||
*
|
||||
* @description
|
||||
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/di/ts/provider_spec.ts region='StaticClassSansProvider'}
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface StaticClassSansProvider {
|
||||
/**
|
||||
* An optional class to instantiate for the `token`. (If not provided `provide` is assumed to be a
|
||||
* class to instantiate)
|
||||
*/
|
||||
useClass: Type<any>;
|
||||
|
||||
/**
|
||||
* A list of `token`s which need to be resolved by the injector. The list of values is then
|
||||
* used as arguments to the `useClass` constructor.
|
||||
*/
|
||||
deps: any[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the {@link Injector} to return an instance of `useClass` for a token.
|
||||
* @howToUse
|
||||
@ -68,25 +118,12 @@ export interface ValueProvider {
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface StaticClassProvider {
|
||||
export interface StaticClassProvider extends StaticClassSansProvider {
|
||||
/**
|
||||
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
|
||||
*/
|
||||
provide: any;
|
||||
|
||||
/**
|
||||
* An optional class to instantiate for the `token`. (If not provided `provide` is assumed to be a
|
||||
* class to
|
||||
* instantiate)
|
||||
*/
|
||||
useClass: Type<any>;
|
||||
|
||||
/**
|
||||
* A list of `token`s which need to be resolved by the injector. The list of values is then
|
||||
* used as arguments to the `useClass` constructor.
|
||||
*/
|
||||
deps: any[];
|
||||
|
||||
/**
|
||||
* If true, then injector returns an array of instances. This is useful to allow multiple
|
||||
* providers spread across many files to provide configuration information to a common token.
|
||||
@ -98,6 +135,31 @@ export interface StaticClassProvider {
|
||||
multi?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the {@link Injector} to return an instance of a token.
|
||||
* @howToUse
|
||||
* ```
|
||||
* @Injectable(SomeModule, {deps: []})
|
||||
* class MyService {}
|
||||
* ```
|
||||
*
|
||||
* @description
|
||||
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/di/ts/provider_spec.ts region='ConstructorSansProvider'}
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface ConstructorSansProvider {
|
||||
/**
|
||||
* A list of `token`s which need to be resolved by the injector. The list of values is then
|
||||
* used as arguments to the `useClass` constructor.
|
||||
*/
|
||||
deps?: any[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the {@link Injector} to return an instance of a token.
|
||||
* @howToUse
|
||||
@ -117,18 +179,12 @@ export interface StaticClassProvider {
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface ConstructorProvider {
|
||||
export interface ConstructorProvider extends ConstructorSansProvider {
|
||||
/**
|
||||
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
|
||||
*/
|
||||
provide: Type<any>;
|
||||
|
||||
/**
|
||||
* A list of `token`s which need to be resolved by the injector. The list of values is then
|
||||
* used as arguments to the `useClass` constructor.
|
||||
*/
|
||||
deps: any[];
|
||||
|
||||
/**
|
||||
* If true, then injector returns an array of instances. This is useful to allow multiple
|
||||
* providers spread across many files to provide configuration information to a common token.
|
||||
@ -140,6 +196,30 @@ export interface ConstructorProvider {
|
||||
multi?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the {@link Injector} to return a value of another `useExisting` token.
|
||||
* @howToUse
|
||||
* ```
|
||||
* @Injectable(SomeModule, {useExisting: 'someOtherToken'})
|
||||
* class SomeClass {}
|
||||
* ```
|
||||
*
|
||||
* @description
|
||||
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/di/ts/provider_spec.ts region='ExistingSansProvider'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface ExistingSansProvider {
|
||||
/**
|
||||
* Existing `token` to return. (equivalent to `injector.get(useExisting)`)
|
||||
*/
|
||||
useExisting: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the {@link Injector} to return a value of another `useExisting` token.
|
||||
* @howToUse
|
||||
@ -156,17 +236,12 @@ export interface ConstructorProvider {
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface ExistingProvider {
|
||||
export interface ExistingProvider extends ExistingSansProvider {
|
||||
/**
|
||||
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
|
||||
*/
|
||||
provide: any;
|
||||
|
||||
/**
|
||||
* Existing `token` to return. (equivalent to `injector.get(useExisting)`)
|
||||
*/
|
||||
useExisting: any;
|
||||
|
||||
/**
|
||||
* If true, then injector returns an array of instances. This is useful to allow multiple
|
||||
* providers spread across many files to provide configuration information to a common token.
|
||||
@ -178,6 +253,40 @@ export interface ExistingProvider {
|
||||
multi?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the {@link Injector} to return a value by invoking a `useFactory`
|
||||
* function.
|
||||
* @howToUse
|
||||
* ```
|
||||
* function serviceFactory() { ... }
|
||||
*
|
||||
* @Injectable(SomeModule, {useFactory: serviceFactory, deps: []})
|
||||
* class SomeClass {}
|
||||
* ```
|
||||
*
|
||||
* @description
|
||||
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/di/ts/provider_spec.ts region='FactorySansProvider'}
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface FactorySansProvider {
|
||||
/**
|
||||
* A function to invoke to create a value for this `token`. The function is invoked with
|
||||
* resolved values of `token`s in the `deps` field.
|
||||
*/
|
||||
useFactory: Function;
|
||||
|
||||
/**
|
||||
* A list of `token`s which need to be resolved by the injector. The list of values is then
|
||||
* used as arguments to the `useFactory` function.
|
||||
*/
|
||||
deps?: any[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the {@link Injector} to return a value by invoking a `useFactory`
|
||||
* function.
|
||||
@ -200,24 +309,12 @@ export interface ExistingProvider {
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface FactoryProvider {
|
||||
export interface FactoryProvider extends FactorySansProvider {
|
||||
/**
|
||||
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
|
||||
*/
|
||||
provide: any;
|
||||
|
||||
/**
|
||||
* A function to invoke to create a value for this `token`. The function is invoked with
|
||||
* resolved values of `token`s in the `deps` field.
|
||||
*/
|
||||
useFactory: Function;
|
||||
|
||||
/**
|
||||
* A list of `token`s which need to be resolved by the injector. The list of values is then
|
||||
* used as arguments to the `useFactory` function.
|
||||
*/
|
||||
deps?: any[];
|
||||
|
||||
/**
|
||||
* If true, then injector returns an array of instances. This is useful to allow multiple
|
||||
* providers spread across many files to provide configuration information to a common token.
|
||||
@ -270,6 +367,34 @@ export type StaticProvider = ValueProvider | ExistingProvider | StaticClassProvi
|
||||
*/
|
||||
export interface TypeProvider extends Type<any> {}
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the {@link Injector} to return a value by invoking a `useClass`
|
||||
* function.
|
||||
* @howToUse
|
||||
* ```
|
||||
*
|
||||
* class SomeClassImpl {}
|
||||
*
|
||||
* @Injectable(SomeModule, {useClass: SomeClassImpl})
|
||||
* class SomeClass {}
|
||||
* ```
|
||||
*
|
||||
* @description
|
||||
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example core/di/ts/provider_spec.ts region='ClassSansProvider'}
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface ClassSansProvider {
|
||||
/**
|
||||
* Class to instantiate for the `token`.
|
||||
*/
|
||||
useClass: Type<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @whatItDoes Configures the {@link Injector} to return an instance of `useClass` for a token.
|
||||
* @howToUse
|
||||
@ -292,17 +417,12 @@ export interface TypeProvider extends Type<any> {}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface ClassProvider {
|
||||
export interface ClassProvider extends ClassSansProvider {
|
||||
/**
|
||||
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
|
||||
*/
|
||||
provide: any;
|
||||
|
||||
/**
|
||||
* Class to instantiate for the `token`.
|
||||
*/
|
||||
useClass: Type<any>;
|
||||
|
||||
/**
|
||||
* If true, then injector returns an array of instances. This is useful to allow multiple
|
||||
* providers spread across many files to provide configuration information to a common token.
|
||||
|
@ -43,18 +43,19 @@ export const PROP_METADATA = '__prop__metadata__';
|
||||
*/
|
||||
export function makeDecorator(
|
||||
name: string, props?: (...args: any[]) => any, parentClass?: any,
|
||||
chainFn?: (fn: Function) => void):
|
||||
chainFn?: (fn: Function) => void, typeFn?: (type: Type<any>, ...args: any[]) => void):
|
||||
{new (...args: any[]): any; (...args: any[]): any; (...args: any[]): (cls: any) => any;} {
|
||||
const metaCtor = makeMetadataCtor(props);
|
||||
|
||||
function DecoratorFactory(objOrType: any): (cls: any) => any {
|
||||
function DecoratorFactory(...args: any[]): (cls: any) => any {
|
||||
if (this instanceof DecoratorFactory) {
|
||||
metaCtor.call(this, objOrType);
|
||||
metaCtor.call(this, ...args);
|
||||
return this;
|
||||
}
|
||||
|
||||
const annotationInstance = new (<any>DecoratorFactory)(objOrType);
|
||||
const annotationInstance = new (<any>DecoratorFactory)(...args);
|
||||
const TypeDecorator: TypeDecorator = <TypeDecorator>function TypeDecorator(cls: Type<any>) {
|
||||
typeFn && typeFn(cls, ...args);
|
||||
// Use of Object.defineProperty is important since it creates non-enumerable property which
|
||||
// prevents the property is copied during subclassing.
|
||||
const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
|
||||
|
16
packages/core/src/util/property.ts
Normal file
16
packages/core/src/util/property.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export function getClosureSafeProperty<T>(objWithPropertyToExtract: T, target: any): string {
|
||||
for (let key in objWithPropertyToExtract) {
|
||||
if (objWithPropertyToExtract[key] === target) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
throw Error('Could not find renamed property on target object.');
|
||||
}
|
@ -7,11 +7,11 @@
|
||||
*/
|
||||
|
||||
import {resolveForwardRef} from '../di/forward_ref';
|
||||
import {Injector} from '../di/injector';
|
||||
import {InjectFlags, Injector, setCurrentInjector} from '../di/injector';
|
||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {stringify} from '../util';
|
||||
|
||||
import {DepDef, DepFlags, NgModuleData, NgModuleDefinition, NgModuleProviderDef, NodeFlags} from './types';
|
||||
import {DepDef, DepFlags, InjectableDef, NgModuleData, NgModuleDefinition, NgModuleProviderDef, NodeFlags} from './types';
|
||||
import {splitDepsDsl, tokenKey} from './util';
|
||||
|
||||
const UNDEFINED_VALUE = new Object();
|
||||
@ -19,6 +19,12 @@ 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 {
|
||||
@ -90,10 +96,32 @@ export function resolveNgModuleDep(
|
||||
_createProviderInstance(data, providerDef);
|
||||
}
|
||||
return providerInstance === UNDEFINED_VALUE ? undefined : providerInstance;
|
||||
} else if (depDef.token.ngInjectableDef && targetsModule(data, depDef.token.ngInjectableDef)) {
|
||||
const injectableDef = depDef.token.ngInjectableDef as InjectableDef;
|
||||
const key = tokenKey;
|
||||
const index = data._providers.length;
|
||||
data._def.providersByKey[depDef.tokenKey] = {
|
||||
flags: NodeFlags.TypeFactoryProvider | NodeFlags.LazyProvider,
|
||||
value: injectableDef.factory,
|
||||
deps: [], index,
|
||||
token: depDef.token,
|
||||
};
|
||||
const former = setCurrentInjector(data);
|
||||
try {
|
||||
data._providers[index] = UNDEFINED_VALUE;
|
||||
return (
|
||||
data._providers[index] =
|
||||
_createProviderInstance(data, data._def.providersByKey[depDef.tokenKey]));
|
||||
} finally {
|
||||
setCurrentInjector(former);
|
||||
}
|
||||
}
|
||||
return data._parent.get(depDef.token, notFoundValue);
|
||||
}
|
||||
|
||||
function targetsModule(ngModule: NgModuleData, def: InjectableDef): boolean {
|
||||
return def.scope != null && ngModule._def.modules.indexOf(def.scope) > -1;
|
||||
}
|
||||
|
||||
function _createProviderInstance(ngModule: NgModuleData, providerDef: NgModuleProviderDef): any {
|
||||
let injectable: any;
|
||||
|
@ -346,50 +346,56 @@ export function resolveDep(
|
||||
elDef = elDef.parent !;
|
||||
}
|
||||
|
||||
while (view) {
|
||||
let searchView: ViewData|null = view;
|
||||
while (searchView) {
|
||||
if (elDef) {
|
||||
switch (tokenKey) {
|
||||
case RendererV1TokenKey: {
|
||||
const compView = findCompView(view, elDef, allowPrivateServices);
|
||||
const compView = findCompView(searchView, elDef, allowPrivateServices);
|
||||
return createRendererV1(compView);
|
||||
}
|
||||
case Renderer2TokenKey: {
|
||||
const compView = findCompView(view, elDef, allowPrivateServices);
|
||||
const compView = findCompView(searchView, elDef, allowPrivateServices);
|
||||
return compView.renderer;
|
||||
}
|
||||
case ElementRefTokenKey:
|
||||
return new ElementRef(asElementData(view, elDef.nodeIndex).renderElement);
|
||||
return new ElementRef(asElementData(searchView, elDef.nodeIndex).renderElement);
|
||||
case ViewContainerRefTokenKey:
|
||||
return asElementData(view, elDef.nodeIndex).viewContainer;
|
||||
return asElementData(searchView, elDef.nodeIndex).viewContainer;
|
||||
case TemplateRefTokenKey: {
|
||||
if (elDef.element !.template) {
|
||||
return asElementData(view, elDef.nodeIndex).template;
|
||||
return asElementData(searchView, elDef.nodeIndex).template;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ChangeDetectorRefTokenKey: {
|
||||
let cdView = findCompView(view, elDef, allowPrivateServices);
|
||||
let cdView = findCompView(searchView, elDef, allowPrivateServices);
|
||||
return createChangeDetectorRef(cdView);
|
||||
}
|
||||
case InjectorRefTokenKey:
|
||||
return createInjector(view, elDef);
|
||||
return createInjector(searchView, elDef);
|
||||
default:
|
||||
const providerDef =
|
||||
(allowPrivateServices ? elDef.element !.allProviders :
|
||||
elDef.element !.publicProviders) ![tokenKey];
|
||||
if (providerDef) {
|
||||
let providerData = asProviderData(view, providerDef.nodeIndex);
|
||||
let providerData = asProviderData(searchView, providerDef.nodeIndex);
|
||||
if (!providerData) {
|
||||
providerData = {instance: _createProviderInstance(view, providerDef)};
|
||||
view.nodes[providerDef.nodeIndex] = providerData as any;
|
||||
providerData = {instance: _createProviderInstance(searchView, providerDef)};
|
||||
searchView.nodes[providerDef.nodeIndex] = providerData as any;
|
||||
}
|
||||
return providerData.instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
allowPrivateServices = isComponentView(view);
|
||||
elDef = viewParentEl(view) !;
|
||||
view = view.parent !;
|
||||
|
||||
allowPrivateServices = isComponentView(searchView);
|
||||
elDef = viewParentEl(searchView) !;
|
||||
searchView = searchView.parent !;
|
||||
|
||||
if (depDef.flags & DepFlags.Self) {
|
||||
searchView = null;
|
||||
}
|
||||
}
|
||||
|
||||
const value = startView.root.injector.get(depDef.token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR);
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {ApplicationRef} from '../application_ref';
|
||||
import {ChangeDetectorRef} from '../change_detection/change_detection';
|
||||
import {Injector} from '../di/injector';
|
||||
import {InjectFlags, Injector} from '../di/injector';
|
||||
import {ComponentFactory, ComponentRef} from '../linker/component_factory';
|
||||
import {ComponentFactoryBoundToModule, ComponentFactoryResolver} from '../linker/component_factory_resolver';
|
||||
import {ElementRef} from '../linker/element_ref';
|
||||
@ -480,6 +480,7 @@ class NgModuleRef_ implements NgModuleData, InternalNgModuleRef<any> {
|
||||
private _destroyed: boolean = false;
|
||||
/** @internal */
|
||||
_providers: any[];
|
||||
/** @internal */
|
||||
_modules: any[];
|
||||
|
||||
readonly injector: Injector = this;
|
||||
@ -490,9 +491,16 @@ class NgModuleRef_ implements NgModuleData, InternalNgModuleRef<any> {
|
||||
initNgModule(this);
|
||||
}
|
||||
|
||||
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
|
||||
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND,
|
||||
injectFlags: InjectFlags = InjectFlags.Default): any {
|
||||
let flags = DepFlags.None;
|
||||
if (injectFlags & InjectFlags.SkipSelf) {
|
||||
flags |= DepFlags.SkipSelf;
|
||||
} else if (injectFlags & InjectFlags.Self) {
|
||||
flags |= DepFlags.Self;
|
||||
}
|
||||
return resolveNgModuleDep(
|
||||
this, {token: token, tokenKey: tokenKey(token), flags: DepFlags.None}, notFoundValue);
|
||||
this, {token: token, tokenKey: tokenKey(token), flags: flags}, notFoundValue);
|
||||
}
|
||||
|
||||
get instance() { return this.get(this._moduleType); }
|
||||
|
@ -292,7 +292,8 @@ export const enum DepFlags {
|
||||
None = 0,
|
||||
SkipSelf = 1 << 0,
|
||||
Optional = 1 << 1,
|
||||
Value = 2 << 2,
|
||||
Self = 1 << 2,
|
||||
Value = 1 << 3,
|
||||
}
|
||||
|
||||
export interface InjectableDef {
|
||||
|
Reference in New Issue
Block a user