feat(core): Create StaticInjector which does not depend on Reflect polyfill.
This commit is contained in:
parent
f69561b2de
commit
d9d00bd9b5
|
@ -18,7 +18,7 @@ export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref';
|
||||||
|
|
||||||
export {Injector} from './di/injector';
|
export {Injector} from './di/injector';
|
||||||
export {ReflectiveInjector} from './di/reflective_injector';
|
export {ReflectiveInjector} from './di/reflective_injector';
|
||||||
export {Provider, TypeProvider, ValueProvider, ClassProvider, ExistingProvider, FactoryProvider} from './di/provider';
|
export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider';
|
||||||
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
|
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
|
||||||
export {ReflectiveKey} from './di/reflective_key';
|
export {ReflectiveKey} from './di/reflective_key';
|
||||||
export {InjectionToken, OpaqueToken} from './di/injection_token';
|
export {InjectionToken, OpaqueToken} from './di/injection_token';
|
||||||
|
|
|
@ -9,7 +9,10 @@
|
||||||
import {Type} from '../type';
|
import {Type} from '../type';
|
||||||
import {stringify} from '../util';
|
import {stringify} from '../util';
|
||||||
|
|
||||||
|
import {resolveForwardRef} from './forward_ref';
|
||||||
import {InjectionToken} from './injection_token';
|
import {InjectionToken} from './injection_token';
|
||||||
|
import {Inject, Optional, Self, SkipSelf} from './metadata';
|
||||||
|
import {ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, StaticProvider, ValueProvider} from './provider';
|
||||||
|
|
||||||
const _THROW_IF_NOT_FOUND = new Object();
|
const _THROW_IF_NOT_FOUND = new Object();
|
||||||
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
|
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
|
||||||
|
@ -17,7 +20,7 @@ export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
|
||||||
class _NullInjector implements Injector {
|
class _NullInjector implements Injector {
|
||||||
get(token: any, notFoundValue: any = _THROW_IF_NOT_FOUND): any {
|
get(token: any, notFoundValue: any = _THROW_IF_NOT_FOUND): any {
|
||||||
if (notFoundValue === _THROW_IF_NOT_FOUND) {
|
if (notFoundValue === _THROW_IF_NOT_FOUND) {
|
||||||
throw new Error(`No provider for ${stringify(token)}!`);
|
throw new Error(`NullInjectorError: No provider for ${stringify(token)}!`);
|
||||||
}
|
}
|
||||||
return notFoundValue;
|
return notFoundValue;
|
||||||
}
|
}
|
||||||
|
@ -60,4 +63,307 @@ export abstract class Injector {
|
||||||
* @suppress {duplicate}
|
* @suppress {duplicate}
|
||||||
*/
|
*/
|
||||||
abstract get(token: any, notFoundValue?: any): any;
|
abstract get(token: any, notFoundValue?: any): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Injector which is configure using `StaticProvider`s.
|
||||||
|
*
|
||||||
|
* ### Example
|
||||||
|
*
|
||||||
|
* {@example core/di/ts/provider_spec.ts region='ConstructorProvider'}
|
||||||
|
*/
|
||||||
|
static create(providers: StaticProvider[], parent?: Injector): Injector {
|
||||||
|
return new StaticInjector(providers, parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const IDENT = function<T>(value: T): T {
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
const EMPTY = <any[]>[];
|
||||||
|
const CIRCULAR = IDENT;
|
||||||
|
const MULTI_PROVIDER_FN = function(): any[] {
|
||||||
|
return Array.prototype.slice.call(arguments);
|
||||||
|
};
|
||||||
|
const GET_PROPERTY_NAME = {} as any;
|
||||||
|
const USE_VALUE =
|
||||||
|
getClosureSafeProperty<ValueProvider>({provide: String, useValue: GET_PROPERTY_NAME});
|
||||||
|
const NG_TOKEN_PATH = 'ngTokenPath';
|
||||||
|
const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath';
|
||||||
|
const enum OptionFlags {
|
||||||
|
Optional = 1 << 0,
|
||||||
|
CheckSelf = 1 << 1,
|
||||||
|
CheckParent = 1 << 2,
|
||||||
|
Default = CheckSelf | CheckParent
|
||||||
|
}
|
||||||
|
const NULL_INJECTOR = Injector.NULL;
|
||||||
|
const NEW_LINE = /\n/gm;
|
||||||
|
const NO_NEW_LINE = 'ɵ';
|
||||||
|
|
||||||
|
export class StaticInjector implements Injector {
|
||||||
|
readonly parent: Injector;
|
||||||
|
|
||||||
|
private _records: Map<any, Record>;
|
||||||
|
|
||||||
|
constructor(providers: StaticProvider[], parent: Injector = NULL_INJECTOR) {
|
||||||
|
this.parent = parent;
|
||||||
|
const records = this._records = new Map<any, Record>();
|
||||||
|
records.set(
|
||||||
|
Injector, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false});
|
||||||
|
recursivelyProcessProviders(records, providers);
|
||||||
|
}
|
||||||
|
|
||||||
|
get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T): T;
|
||||||
|
get(token: any, notFoundValue?: any): any;
|
||||||
|
get(token: any, notFoundValue?: any): any {
|
||||||
|
const record = this._records.get(token);
|
||||||
|
try {
|
||||||
|
return tryResolveToken(token, record, this._records, this.parent, notFoundValue);
|
||||||
|
} catch (e) {
|
||||||
|
const tokenPath: any[] = e[NG_TEMP_TOKEN_PATH];
|
||||||
|
e.message = formatError('\n' + e.message, tokenPath);
|
||||||
|
e[NG_TOKEN_PATH] = tokenPath;
|
||||||
|
e[NG_TEMP_TOKEN_PATH] = null;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
const tokens = <string[]>[], records = this._records;
|
||||||
|
records.forEach((v, token) => tokens.push(stringify(token)));
|
||||||
|
return `StaticInjector[${tokens.join(', ')}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SupportedProvider =
|
||||||
|
ValueProvider | ExistingProvider | StaticClassProvider | ConstructorProvider | FactoryProvider;
|
||||||
|
|
||||||
|
interface Record {
|
||||||
|
fn: Function;
|
||||||
|
useNew: boolean;
|
||||||
|
deps: DependencyRecord[];
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DependencyRecord {
|
||||||
|
token: any;
|
||||||
|
options: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenPath = Array<any>;
|
||||||
|
|
||||||
|
function resolveProvider(provider: SupportedProvider): Record {
|
||||||
|
const deps = computeDeps(provider);
|
||||||
|
let fn: Function = IDENT;
|
||||||
|
let value: any = EMPTY;
|
||||||
|
let useNew: boolean = false;
|
||||||
|
let provide = resolveForwardRef(provider.provide);
|
||||||
|
if (USE_VALUE in provider) {
|
||||||
|
// We need to use USE_VALUE in provider since provider.useValue could be defined as undefined.
|
||||||
|
value = (provider as ValueProvider).useValue;
|
||||||
|
} else if ((provider as FactoryProvider).useFactory) {
|
||||||
|
fn = (provider as FactoryProvider).useFactory;
|
||||||
|
} else if ((provider as ExistingProvider).useExisting) {
|
||||||
|
// Just use IDENT
|
||||||
|
} else if ((provider as StaticClassProvider).useClass) {
|
||||||
|
useNew = true;
|
||||||
|
fn = resolveForwardRef((provider as StaticClassProvider).useClass);
|
||||||
|
} else if (typeof provide == 'function') {
|
||||||
|
useNew = true;
|
||||||
|
fn = provide;
|
||||||
|
} else {
|
||||||
|
throw staticError(
|
||||||
|
'StaticProvider does not have [useValue|useFactory|useExisting|useClass] or [provide] is not newable',
|
||||||
|
provider);
|
||||||
|
}
|
||||||
|
return {deps, fn, useNew, value};
|
||||||
|
}
|
||||||
|
|
||||||
|
function multiProviderMixError(token: any) {
|
||||||
|
return staticError('Cannot mix multi providers and regular providers', token);
|
||||||
|
}
|
||||||
|
|
||||||
|
function recursivelyProcessProviders(records: Map<any, Record>, provider: StaticProvider) {
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
} else if (typeof provider === 'function') {
|
||||||
|
// Functions were supported in ReflectiveInjector, but are not here. For safety give useful
|
||||||
|
// error messages
|
||||||
|
throw staticError('Function/Class not supported', provider);
|
||||||
|
} else if (provider && typeof provider === 'object' && provider.provide) {
|
||||||
|
// At this point we have what looks like a provider: {provide: ?, ....}
|
||||||
|
let token = resolveForwardRef(provider.provide);
|
||||||
|
const resolvedProvider = resolveProvider(provider);
|
||||||
|
if (provider.multi === true) {
|
||||||
|
// This is a multi provider.
|
||||||
|
let multiProvider: Record|undefined = records.get(token);
|
||||||
|
if (multiProvider) {
|
||||||
|
if (multiProvider.fn !== MULTI_PROVIDER_FN) {
|
||||||
|
throw multiProviderMixError(token);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create a placeholder factory which will look up the constituents of the multi provider.
|
||||||
|
records.set(token, multiProvider = <Record>{
|
||||||
|
token: provider.provide,
|
||||||
|
deps: [],
|
||||||
|
useNew: false,
|
||||||
|
fn: MULTI_PROVIDER_FN,
|
||||||
|
value: EMPTY
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Treat the provider as the token.
|
||||||
|
token = provider;
|
||||||
|
multiProvider.deps.push({token, options: OptionFlags.Default});
|
||||||
|
}
|
||||||
|
const record = records.get(token);
|
||||||
|
if (record && record.fn == MULTI_PROVIDER_FN) {
|
||||||
|
throw multiProviderMixError(token);
|
||||||
|
}
|
||||||
|
records.set(token, resolvedProvider);
|
||||||
|
} else {
|
||||||
|
throw staticError('Unexpected provider', provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryResolveToken(
|
||||||
|
token: any, record: Record | undefined, records: Map<any, Record>, parent: Injector,
|
||||||
|
notFoundValue: any): any {
|
||||||
|
try {
|
||||||
|
return resolveToken(token, record, records, parent, notFoundValue);
|
||||||
|
} catch (e) {
|
||||||
|
// ensure that 'e' is of type Error.
|
||||||
|
if (!(e instanceof Error)) {
|
||||||
|
e = new Error(e);
|
||||||
|
}
|
||||||
|
const path: any[] = e[NG_TEMP_TOKEN_PATH] = e[NG_TEMP_TOKEN_PATH] || [];
|
||||||
|
path.unshift(token);
|
||||||
|
if (record && record.value == CIRCULAR) {
|
||||||
|
// Reset the Circular flag.
|
||||||
|
record.value = EMPTY;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveToken(
|
||||||
|
token: any, record: Record | undefined, records: Map<any, Record>, parent: Injector,
|
||||||
|
notFoundValue: any): any {
|
||||||
|
let value;
|
||||||
|
if (record) {
|
||||||
|
// 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;
|
||||||
|
if (value == CIRCULAR) {
|
||||||
|
throw Error(NO_NEW_LINE + 'Circular dependency');
|
||||||
|
} else if (value === EMPTY) {
|
||||||
|
record.value = CIRCULAR;
|
||||||
|
let obj = undefined;
|
||||||
|
let useNew = record.useNew;
|
||||||
|
let fn = record.fn;
|
||||||
|
let depRecords = record.deps;
|
||||||
|
let deps = EMPTY;
|
||||||
|
if (depRecords.length) {
|
||||||
|
deps = [];
|
||||||
|
for (let i = 0; i < depRecords.length; i++) {
|
||||||
|
const depRecord: DependencyRecord = depRecords[i];
|
||||||
|
const options = depRecord.options;
|
||||||
|
const childRecord =
|
||||||
|
options & OptionFlags.CheckSelf ? records.get(depRecord.token) : undefined;
|
||||||
|
deps.push(tryResolveToken(
|
||||||
|
// Current Token to resolve
|
||||||
|
depRecord.token,
|
||||||
|
// A record which describes how to resolve the token.
|
||||||
|
// If undefined, this means we don't have such a record
|
||||||
|
childRecord,
|
||||||
|
// Other records we know about.
|
||||||
|
records,
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
record.value = value = useNew ? new (fn as any)(...deps) : fn.apply(obj, deps);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = parent.get(token, notFoundValue);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function computeDeps(provider: StaticProvider): DependencyRecord[] {
|
||||||
|
let deps: DependencyRecord[] = EMPTY;
|
||||||
|
const providerDeps: any[] =
|
||||||
|
(provider as ExistingProvider & StaticClassProvider & ConstructorProvider).deps;
|
||||||
|
if (providerDeps && providerDeps.length) {
|
||||||
|
deps = [];
|
||||||
|
for (let i = 0; i < providerDeps.length; i++) {
|
||||||
|
let options = OptionFlags.Default;
|
||||||
|
let token = resolveForwardRef(providerDeps[i]);
|
||||||
|
if (token instanceof Array) {
|
||||||
|
for (let j = 0, annotations = token; j < annotations.length; j++) {
|
||||||
|
const annotation = annotations[j];
|
||||||
|
if (annotation instanceof Optional || annotation == Optional) {
|
||||||
|
options = options | OptionFlags.Optional;
|
||||||
|
} else if (annotation instanceof SkipSelf || annotation == SkipSelf) {
|
||||||
|
options = options & ~OptionFlags.CheckSelf;
|
||||||
|
} else if (annotation instanceof Self || annotation == Self) {
|
||||||
|
options = options & ~OptionFlags.CheckParent;
|
||||||
|
} else if (annotation instanceof Inject) {
|
||||||
|
token = (annotation as Inject).token;
|
||||||
|
} else {
|
||||||
|
token = resolveForwardRef(annotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deps.push({token, options});
|
||||||
|
}
|
||||||
|
} else if ((provider as ExistingProvider).useExisting) {
|
||||||
|
const token = resolveForwardRef((provider as ExistingProvider).useExisting);
|
||||||
|
deps = [{token, options: OptionFlags.Default}];
|
||||||
|
} else if (!providerDeps && !(USE_VALUE in provider)) {
|
||||||
|
// useValue & useExisting are the only ones which are exempt from deps all others need it.
|
||||||
|
throw staticError('\'deps\' required', provider);
|
||||||
|
}
|
||||||
|
return deps;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatError(text: string, obj: any): string {
|
||||||
|
text = text && text.charAt(0) === '\n' && text.charAt(1) == NO_NEW_LINE ? text.substr(2) : text;
|
||||||
|
let context = stringify(obj);
|
||||||
|
if (obj instanceof Array) {
|
||||||
|
context = obj.map(stringify).join(' -> ');
|
||||||
|
} else if (typeof obj === 'object') {
|
||||||
|
let parts = <string[]>[];
|
||||||
|
for (let key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key)) {
|
||||||
|
let value = obj[key];
|
||||||
|
parts.push(
|
||||||
|
key + ':' + (typeof value === 'string' ? JSON.stringify(value) : stringify(value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context = `{${parts.join(', ')}}`;
|
||||||
|
}
|
||||||
|
return `StaticInjectorError[${context}]: ${text.replace(NEW_LINE, '\n ')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function staticError(text: string, obj: any): Error {
|
||||||
|
return new Error(formatError(text, obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClosureSafeProperty<T>(objWithPropertyToExtract: T): string {
|
||||||
|
for (let key in objWithPropertyToExtract) {
|
||||||
|
if (objWithPropertyToExtract[key] === GET_PROPERTY_NAME) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw Error('!prop');
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,32 +8,6 @@
|
||||||
|
|
||||||
import {Type} from '../type';
|
import {Type} from '../type';
|
||||||
|
|
||||||
/**
|
|
||||||
* @whatItDoes Configures the {@link Injector} to return an instance of `Type` when `Type' is used
|
|
||||||
* as token.
|
|
||||||
* @howToUse
|
|
||||||
* ```
|
|
||||||
* @Injectable()
|
|
||||||
* class MyService {}
|
|
||||||
*
|
|
||||||
* const provider: TypeProvider = MyService;
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
*
|
|
||||||
* Create an instance by invoking the `new` operator and supplying additional arguments.
|
|
||||||
* This form is a short form of `TypeProvider`;
|
|
||||||
*
|
|
||||||
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
|
||||||
*
|
|
||||||
* ### Example
|
|
||||||
*
|
|
||||||
* {@example core/di/ts/provider_spec.ts region='TypeProvider'}
|
|
||||||
*
|
|
||||||
* @stable
|
|
||||||
*/
|
|
||||||
export interface TypeProvider extends Type<any> {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @whatItDoes Configures the {@link Injector} to return a value for a token.
|
* @whatItDoes Configures the {@link Injector} to return a value for a token.
|
||||||
* @howToUse
|
* @howToUse
|
||||||
|
@ -79,7 +53,7 @@ export interface ValueProvider {
|
||||||
* @Injectable()
|
* @Injectable()
|
||||||
* class MyService {}
|
* class MyService {}
|
||||||
*
|
*
|
||||||
* const provider: ClassProvider = {provide: 'someToken', useClass: MyService};
|
* const provider: ClassProvider = {provide: 'someToken', useClass: MyService, deps: []};
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
|
@ -87,24 +61,74 @@ export interface ValueProvider {
|
||||||
*
|
*
|
||||||
* ### Example
|
* ### Example
|
||||||
*
|
*
|
||||||
* {@example core/di/ts/provider_spec.ts region='ClassProvider'}
|
* {@example core/di/ts/provider_spec.ts region='StaticClassProvider'}
|
||||||
*
|
*
|
||||||
* Note that following two providers are not equal:
|
* Note that following two providers are not equal:
|
||||||
* {@example core/di/ts/provider_spec.ts region='ClassProviderDifference'}
|
* {@example core/di/ts/provider_spec.ts region='StaticClassProviderDifference'}
|
||||||
*
|
*
|
||||||
* @stable
|
* @stable
|
||||||
*/
|
*/
|
||||||
export interface ClassProvider {
|
export interface StaticClassProvider {
|
||||||
/**
|
/**
|
||||||
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
|
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
|
||||||
*/
|
*/
|
||||||
provide: any;
|
provide: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to instantiate for the `token`.
|
* An optional class to instantiate for the `token`. (If not provided `provide` is assumed to be a
|
||||||
|
* class to
|
||||||
|
* instantiate)
|
||||||
*/
|
*/
|
||||||
useClass: Type<any>;
|
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.
|
||||||
|
*
|
||||||
|
* ### Example
|
||||||
|
*
|
||||||
|
* {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'}
|
||||||
|
*/
|
||||||
|
multi?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @whatItDoes Configures the {@link Injector} to return an instance of a token.
|
||||||
|
* @howToUse
|
||||||
|
* ```
|
||||||
|
* @Injectable()
|
||||||
|
* class MyService {}
|
||||||
|
*
|
||||||
|
* const provider: ClassProvider = {provide: MyClass, deps: []};
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
||||||
|
*
|
||||||
|
* ### Example
|
||||||
|
*
|
||||||
|
* {@example core/di/ts/provider_spec.ts region='ConstructorProvider'}
|
||||||
|
*
|
||||||
|
* @stable
|
||||||
|
*/
|
||||||
|
export interface ConstructorProvider {
|
||||||
|
/**
|
||||||
|
* 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
|
* 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.
|
* providers spread across many files to provide configuration information to a common token.
|
||||||
|
@ -205,11 +229,95 @@ export interface FactoryProvider {
|
||||||
multi?: boolean;
|
multi?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @whatItDoes Describes how the {@link Injector} should be configured in a static way (Without
|
||||||
|
* reflection).
|
||||||
|
* @howToUse
|
||||||
|
* See {@link ValueProvider}, {@link ExistingProvider}, {@link FactoryProvider}.
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
||||||
|
*
|
||||||
|
* @stable
|
||||||
|
*/
|
||||||
|
export type StaticProvider = ValueProvider | ExistingProvider | StaticClassProvider |
|
||||||
|
ConstructorProvider | FactoryProvider | any[];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @whatItDoes Configures the {@link Injector} to return an instance of `Type` when `Type' is used
|
||||||
|
* as token.
|
||||||
|
* @howToUse
|
||||||
|
* ```
|
||||||
|
* @Injectable()
|
||||||
|
* class MyService {}
|
||||||
|
*
|
||||||
|
* const provider: TypeProvider = MyService;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
*
|
||||||
|
* Create an instance by invoking the `new` operator and supplying additional arguments.
|
||||||
|
* This form is a short form of `TypeProvider`;
|
||||||
|
*
|
||||||
|
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
||||||
|
*
|
||||||
|
* ### Example
|
||||||
|
*
|
||||||
|
* {@example core/di/ts/provider_spec.ts region='TypeProvider'}
|
||||||
|
*
|
||||||
|
* @stable
|
||||||
|
*/
|
||||||
|
export interface TypeProvider extends Type<any> {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @whatItDoes Configures the {@link Injector} to return an instance of `useClass` for a token.
|
||||||
|
* @howToUse
|
||||||
|
* ```
|
||||||
|
* @Injectable()
|
||||||
|
* class MyService {}
|
||||||
|
*
|
||||||
|
* const provider: ClassProvider = {provide: 'someToken', useClass: MyService};
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
||||||
|
*
|
||||||
|
* ### Example
|
||||||
|
*
|
||||||
|
* {@example core/di/ts/provider_spec.ts region='ClassProvider'}
|
||||||
|
*
|
||||||
|
* Note that following two providers are not equal:
|
||||||
|
* {@example core/di/ts/provider_spec.ts region='ClassProviderDifference'}
|
||||||
|
*
|
||||||
|
* @stable
|
||||||
|
*/
|
||||||
|
export interface ClassProvider {
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* ### Example
|
||||||
|
*
|
||||||
|
* {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'}
|
||||||
|
*/
|
||||||
|
multi?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @whatItDoes Describes how the {@link Injector} should be configured.
|
* @whatItDoes Describes how the {@link Injector} should be configured.
|
||||||
* @howToUse
|
* @howToUse
|
||||||
* See {@link TypeProvider}, {@link ValueProvider}, {@link ClassProvider}, {@link ExistingProvider},
|
* See {@link TypeProvider}, {@link ClassProvider}, {@link StaticProvider}.
|
||||||
* {@link FactoryProvider}.
|
|
||||||
*
|
*
|
||||||
* @description
|
* @description
|
||||||
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
* For more details, see the {@linkDocs guide/dependency-injection "Dependency Injection Guide"}.
|
||||||
|
|
|
@ -13,6 +13,8 @@ import {cyclicDependencyError, instantiationError, noProviderError, outOfBoundsE
|
||||||
import {ReflectiveKey} from './reflective_key';
|
import {ReflectiveKey} from './reflective_key';
|
||||||
import {ReflectiveDependency, ResolvedReflectiveFactory, ResolvedReflectiveProvider, resolveReflectiveProviders} from './reflective_provider';
|
import {ReflectiveDependency, ResolvedReflectiveFactory, ResolvedReflectiveProvider, resolveReflectiveProviders} from './reflective_provider';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Threshold for the dynamic version
|
// Threshold for the dynamic version
|
||||||
const UNDEFINED = new Object();
|
const UNDEFINED = new Object();
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import {resolveForwardRef} from './forward_ref';
|
||||||
* `Key` should not be created directly. {@link ReflectiveInjector} creates keys automatically when
|
* `Key` should not be created directly. {@link ReflectiveInjector} creates keys automatically when
|
||||||
* resolving
|
* resolving
|
||||||
* providers.
|
* providers.
|
||||||
* @experimental
|
* @deprecated No replacement
|
||||||
*/
|
*/
|
||||||
export class ReflectiveKey {
|
export class ReflectiveKey {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -60,6 +60,10 @@ export function stringify(token: any): string {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (token instanceof Array) {
|
||||||
|
return '[' + token.map(stringify).join(', ') + ']';
|
||||||
|
}
|
||||||
|
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
return '' + token;
|
return '' + token;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,13 @@ import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('Injector.NULL', () => {
|
describe('Injector.NULL', () => {
|
||||||
it('should throw if no arg is given', () => {
|
it('should throw if no arg is given', () => {
|
||||||
expect(() => Injector.NULL.get('someToken')).toThrowError('No provider for someToken!');
|
expect(() => Injector.NULL.get('someToken'))
|
||||||
|
.toThrowError('NullInjectorError: No provider for someToken!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw if THROW_IF_NOT_FOUND is given', () => {
|
it('should throw if THROW_IF_NOT_FOUND is given', () => {
|
||||||
expect(() => Injector.NULL.get('someToken', Injector.THROW_IF_NOT_FOUND))
|
expect(() => Injector.NULL.get('someToken', Injector.THROW_IF_NOT_FOUND))
|
||||||
.toThrowError('No provider for someToken!');
|
.toThrowError('NullInjectorError: No provider for someToken!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the default value',
|
it('should return the default value',
|
||||||
|
|
|
@ -0,0 +1,479 @@
|
||||||
|
/**
|
||||||
|
* @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 {Inject, InjectionToken, Injector, Optional, ReflectiveKey, Self, SkipSelf, forwardRef} from '@angular/core';
|
||||||
|
import {getOriginalError} from '@angular/core/src/errors';
|
||||||
|
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||||
|
|
||||||
|
import {stringify} from '../../src/util';
|
||||||
|
|
||||||
|
class Engine {
|
||||||
|
static PROVIDER = {provide: Engine, useClass: Engine, deps: []};
|
||||||
|
}
|
||||||
|
|
||||||
|
class BrokenEngine {
|
||||||
|
static PROVIDER = {provide: Engine, useClass: BrokenEngine, deps: []};
|
||||||
|
constructor() { throw new Error('Broken Engine'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
class DashboardSoftware {
|
||||||
|
static PROVIDER = {provide: DashboardSoftware, useClass: DashboardSoftware, deps: []};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Dashboard {
|
||||||
|
static PROVIDER = {provide: Dashboard, useClass: Dashboard, deps: [DashboardSoftware]};
|
||||||
|
constructor(software: DashboardSoftware) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TurboEngine extends Engine {
|
||||||
|
static PROVIDER = {provide: Engine, useClass: TurboEngine, deps: []};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Car {
|
||||||
|
static PROVIDER = {provide: Car, useClass: Car, deps: [Engine]};
|
||||||
|
constructor(public engine: Engine) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CarWithOptionalEngine {
|
||||||
|
static PROVIDER = {
|
||||||
|
provide: CarWithOptionalEngine,
|
||||||
|
useClass: CarWithOptionalEngine,
|
||||||
|
deps: [[new Optional(), Engine]]
|
||||||
|
};
|
||||||
|
constructor(public engine: Engine) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CarWithDashboard {
|
||||||
|
static PROVIDER = {
|
||||||
|
provide: CarWithDashboard,
|
||||||
|
useClass: CarWithDashboard,
|
||||||
|
deps: [Engine, Dashboard]
|
||||||
|
};
|
||||||
|
engine: Engine;
|
||||||
|
dashboard: Dashboard;
|
||||||
|
constructor(engine: Engine, dashboard: Dashboard) {
|
||||||
|
this.engine = engine;
|
||||||
|
this.dashboard = dashboard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SportsCar extends Car {
|
||||||
|
static PROVIDER = {provide: Car, useClass: SportsCar, deps: [Engine]};
|
||||||
|
}
|
||||||
|
|
||||||
|
class CyclicEngine {
|
||||||
|
static PROVIDER = {provide: Engine, useClass: CyclicEngine, deps: [Car]};
|
||||||
|
constructor(car: Car) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoAnnotations {
|
||||||
|
constructor(secretDependency: any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function factoryFn(a: any) {}
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
const dynamicProviders = [
|
||||||
|
{provide: 'provider0', useValue: 1}, {provide: 'provider1', useValue: 1},
|
||||||
|
{provide: 'provider2', useValue: 1}, {provide: 'provider3', useValue: 1},
|
||||||
|
{provide: 'provider4', useValue: 1}, {provide: 'provider5', useValue: 1},
|
||||||
|
{provide: 'provider6', useValue: 1}, {provide: 'provider7', useValue: 1},
|
||||||
|
{provide: 'provider8', useValue: 1}, {provide: 'provider9', useValue: 1},
|
||||||
|
{provide: 'provider10', useValue: 1}
|
||||||
|
];
|
||||||
|
|
||||||
|
describe(`StaticInjector`, () => {
|
||||||
|
|
||||||
|
it('should instantiate a class without dependencies', () => {
|
||||||
|
const injector = Injector.create([Engine.PROVIDER]);
|
||||||
|
const engine = injector.get(Engine);
|
||||||
|
|
||||||
|
expect(engine).toBeAnInstanceOf(Engine);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve dependencies based on type information', () => {
|
||||||
|
const injector = Injector.create([Engine.PROVIDER, Car.PROVIDER]);
|
||||||
|
const car = injector.get(Car);
|
||||||
|
|
||||||
|
expect(car).toBeAnInstanceOf(Car);
|
||||||
|
expect(car.engine).toBeAnInstanceOf(Engine);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should cache instances', () => {
|
||||||
|
const injector = Injector.create([Engine.PROVIDER]);
|
||||||
|
|
||||||
|
const e1 = injector.get(Engine);
|
||||||
|
const e2 = injector.get(Engine);
|
||||||
|
|
||||||
|
expect(e1).toBe(e2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide to a value', () => {
|
||||||
|
const injector = Injector.create([{provide: Engine, useValue: 'fake engine'}]);
|
||||||
|
|
||||||
|
const engine = injector.get(Engine);
|
||||||
|
expect(engine).toEqual('fake engine');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject dependencies instance of InjectionToken', () => {
|
||||||
|
const TOKEN = new InjectionToken<string>('token');
|
||||||
|
|
||||||
|
const injector = Injector.create([
|
||||||
|
{provide: TOKEN, useValue: 'by token'},
|
||||||
|
{provide: Engine, useFactory: (v: string) => v, deps: [[TOKEN]]},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const engine = injector.get(Engine);
|
||||||
|
expect(engine).toEqual('by token');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide to a factory', () => {
|
||||||
|
function sportsCarFactory(e: any) { return new SportsCar(e); }
|
||||||
|
|
||||||
|
const injector = Injector.create(
|
||||||
|
[Engine.PROVIDER, {provide: Car, useFactory: sportsCarFactory, deps: [Engine]}]);
|
||||||
|
|
||||||
|
const car = injector.get(Car);
|
||||||
|
expect(car).toBeAnInstanceOf(SportsCar);
|
||||||
|
expect(car.engine).toBeAnInstanceOf(Engine);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should supporting provider to null', () => {
|
||||||
|
const injector = Injector.create([{provide: Engine, useValue: null}]);
|
||||||
|
const engine = injector.get(Engine);
|
||||||
|
expect(engine).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide to an alias', () => {
|
||||||
|
const injector = Injector.create([
|
||||||
|
Engine.PROVIDER, {provide: SportsCar, useClass: SportsCar, deps: [Engine]},
|
||||||
|
{provide: Car, useExisting: SportsCar}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const car = injector.get(Car);
|
||||||
|
const sportsCar = injector.get(SportsCar);
|
||||||
|
expect(car).toBeAnInstanceOf(SportsCar);
|
||||||
|
expect(car).toBe(sportsCar);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support multiProviders', () => {
|
||||||
|
const injector = Injector.create([
|
||||||
|
Engine.PROVIDER, {provide: Car, useClass: SportsCar, deps: [Engine], multi: true},
|
||||||
|
{provide: Car, useClass: CarWithOptionalEngine, deps: [Engine], multi: true}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const cars = injector.get(Car) as any as Car[];
|
||||||
|
expect(cars.length).toEqual(2);
|
||||||
|
expect(cars[0]).toBeAnInstanceOf(SportsCar);
|
||||||
|
expect(cars[1]).toBeAnInstanceOf(CarWithOptionalEngine);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support multiProviders that are created using useExisting', () => {
|
||||||
|
const injector = Injector.create([
|
||||||
|
Engine.PROVIDER, {provide: SportsCar, useClass: SportsCar, deps: [Engine]},
|
||||||
|
{provide: Car, useExisting: SportsCar, multi: true}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const cars = injector.get(Car) as any as Car[];
|
||||||
|
expect(cars.length).toEqual(1);
|
||||||
|
expect(cars[0]).toBe(injector.get(SportsCar));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when the aliased provider does not exist', () => {
|
||||||
|
const injector = Injector.create([{provide: 'car', useExisting: SportsCar}]);
|
||||||
|
const e =
|
||||||
|
`StaticInjectorError[car -> ${stringify(SportsCar)}]: \n NullInjectorError: No provider for ${stringify(SportsCar)}!`;
|
||||||
|
expect(() => injector.get('car')).toThrowError(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle forwardRef in useExisting', () => {
|
||||||
|
const injector = Injector.create([
|
||||||
|
{provide: 'originalEngine', useClass: forwardRef(() => Engine), deps: []}, {
|
||||||
|
provide: 'aliasedEngine',
|
||||||
|
useExisting: <any>forwardRef(() => 'originalEngine'),
|
||||||
|
deps: []
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
expect(injector.get('aliasedEngine')).toBeAnInstanceOf(Engine);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support overriding factory dependencies', () => {
|
||||||
|
const injector = Injector.create([
|
||||||
|
Engine.PROVIDER,
|
||||||
|
{provide: Car, useFactory: (e: Engine) => new SportsCar(e), deps: [Engine]}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const car = injector.get(Car);
|
||||||
|
expect(car).toBeAnInstanceOf(SportsCar);
|
||||||
|
expect(car.engine).toBeAnInstanceOf(Engine);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support optional dependencies', () => {
|
||||||
|
const injector = Injector.create([CarWithOptionalEngine.PROVIDER]);
|
||||||
|
|
||||||
|
const car = injector.get(CarWithOptionalEngine);
|
||||||
|
expect(car.engine).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should flatten passed-in providers', () => {
|
||||||
|
const injector = Injector.create([[[Engine.PROVIDER, Car.PROVIDER]]]);
|
||||||
|
|
||||||
|
const car = injector.get(Car);
|
||||||
|
expect(car).toBeAnInstanceOf(Car);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use the last provider when there are multiple providers for same token', () => {
|
||||||
|
const injector = Injector.create([
|
||||||
|
{provide: Engine, useClass: Engine, deps: []},
|
||||||
|
{provide: Engine, useClass: TurboEngine, deps: []}
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(injector.get(Engine)).toBeAnInstanceOf(TurboEngine);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use non-type tokens', () => {
|
||||||
|
const injector = Injector.create([{provide: 'token', useValue: 'value'}]);
|
||||||
|
|
||||||
|
expect(injector.get('token')).toEqual('value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when given invalid providers', () => {
|
||||||
|
expect(() => Injector.create(<any>['blah']))
|
||||||
|
.toThrowError('StaticInjectorError[blah]: Unexpected provider');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when missing deps', () => {
|
||||||
|
expect(() => Injector.create(<any>[{provide: Engine, useClass: Engine}]))
|
||||||
|
.toThrowError(
|
||||||
|
'StaticInjectorError[{provide:Engine, useClass:Engine}]: \'deps\' required');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when using reflective API', () => {
|
||||||
|
expect(() => Injector.create(<any>[Engine]))
|
||||||
|
.toThrowError('StaticInjectorError[Engine]: Function/Class not supported');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when unknown provider shape API', () => {
|
||||||
|
expect(() => Injector.create(<any>[{provide: 'abc', deps: [Engine]}]))
|
||||||
|
.toThrowError(
|
||||||
|
'StaticInjectorError[{provide:"abc", deps:[Engine]}]: StaticProvider does not have [useValue|useFactory|useExisting|useClass] or [provide] is not newable');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when given invalid providers and serialize the provider', () => {
|
||||||
|
expect(() => Injector.create(<any>[{foo: 'bar', bar: Car}]))
|
||||||
|
.toThrowError('StaticInjectorError[{foo:"bar", bar:Car}]: Unexpected provider');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide itself', () => {
|
||||||
|
const parent = Injector.create([]);
|
||||||
|
const child = Injector.create([], parent);
|
||||||
|
|
||||||
|
expect(child.get(Injector)).toBe(child);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when no provider defined', () => {
|
||||||
|
const injector = Injector.create([]);
|
||||||
|
expect(() => injector.get('NonExisting'))
|
||||||
|
.toThrowError(
|
||||||
|
'StaticInjectorError[NonExisting]: \n NullInjectorError: No provider for NonExisting!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show the full path when no provider', () => {
|
||||||
|
const injector =
|
||||||
|
Injector.create([CarWithDashboard.PROVIDER, Engine.PROVIDER, Dashboard.PROVIDER]);
|
||||||
|
expect(() => injector.get(CarWithDashboard))
|
||||||
|
.toThrowError(
|
||||||
|
`StaticInjectorError[${stringify(CarWithDashboard)} -> ${stringify(Dashboard)} -> DashboardSoftware]:
|
||||||
|
NullInjectorError: No provider for DashboardSoftware!`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when trying to instantiate a cyclic dependency', () => {
|
||||||
|
const injector = Injector.create([Car.PROVIDER, CyclicEngine.PROVIDER]);
|
||||||
|
|
||||||
|
expect(() => injector.get(Car))
|
||||||
|
.toThrowError(
|
||||||
|
`StaticInjectorError[${stringify(Car)} -> ${stringify(Engine)} -> ${stringify(Car)}]: Circular dependency`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show the full path when error happens in a constructor', () => {
|
||||||
|
const error = new Error('MyError');
|
||||||
|
const injector = Injector.create(
|
||||||
|
[Car.PROVIDER, {provide: Engine, useFactory: () => { throw error; }, deps: []}]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
injector.get(Car);
|
||||||
|
throw 'Must throw';
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).toBe(error);
|
||||||
|
expect(e.message).toContain(
|
||||||
|
`StaticInjectorError[${stringify(Car)} -> Engine]: \n MyError`);
|
||||||
|
expect(e.ngTokenPath[0]).toEqual(Car);
|
||||||
|
expect(e.ngTokenPath[1]).toEqual(Engine);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should instantiate an object after a failed attempt', () => {
|
||||||
|
let isBroken = true;
|
||||||
|
|
||||||
|
const injector = Injector.create([
|
||||||
|
Car.PROVIDER, {
|
||||||
|
provide: Engine,
|
||||||
|
useFactory: (() => isBroken ? new BrokenEngine() : new Engine()),
|
||||||
|
deps: []
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(() => injector.get(Car))
|
||||||
|
.toThrowError('StaticInjectorError[Car -> Engine]: \n Broken Engine');
|
||||||
|
|
||||||
|
isBroken = false;
|
||||||
|
|
||||||
|
expect(injector.get(Car)).toBeAnInstanceOf(Car);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support null/undefined values', () => {
|
||||||
|
const injector = Injector.create([
|
||||||
|
{provide: 'null', useValue: null},
|
||||||
|
{provide: 'undefined', useValue: undefined},
|
||||||
|
]);
|
||||||
|
expect(injector.get('null')).toBe(null);
|
||||||
|
expect(injector.get('undefined')).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('child', () => {
|
||||||
|
it('should load instances from parent injector', () => {
|
||||||
|
const parent = Injector.create([Engine.PROVIDER]);
|
||||||
|
const child = Injector.create([], parent);
|
||||||
|
|
||||||
|
const engineFromParent = parent.get(Engine);
|
||||||
|
const engineFromChild = child.get(Engine);
|
||||||
|
|
||||||
|
expect(engineFromChild).toBe(engineFromParent);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not use the child providers when resolving the dependencies of a parent provider',
|
||||||
|
() => {
|
||||||
|
const parent = Injector.create([Car.PROVIDER, Engine.PROVIDER]);
|
||||||
|
const child = Injector.create([TurboEngine.PROVIDER], parent);
|
||||||
|
|
||||||
|
const carFromChild = child.get(Car);
|
||||||
|
expect(carFromChild.engine).toBeAnInstanceOf(Engine);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create new instance in a child injector', () => {
|
||||||
|
const parent = Injector.create([Engine.PROVIDER]);
|
||||||
|
const child = Injector.create([TurboEngine.PROVIDER], parent);
|
||||||
|
|
||||||
|
const engineFromParent = parent.get(Engine);
|
||||||
|
const engineFromChild = child.get(Engine);
|
||||||
|
|
||||||
|
expect(engineFromParent).not.toBe(engineFromChild);
|
||||||
|
expect(engineFromChild).toBeAnInstanceOf(TurboEngine);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should give access to parent', () => {
|
||||||
|
const parent = Injector.create([]);
|
||||||
|
const child = Injector.create([], parent);
|
||||||
|
expect((child as any).parent).toBe(parent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('instantiate', () => {
|
||||||
|
it('should instantiate an object in the context of the injector', () => {
|
||||||
|
const inj = Injector.create([Engine.PROVIDER]);
|
||||||
|
const childInj = Injector.create([Car.PROVIDER], inj);
|
||||||
|
const car = childInj.get(Car);
|
||||||
|
expect(car).toBeAnInstanceOf(Car);
|
||||||
|
expect(car.engine).toBe(inj.get(Engine));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('depedency resolution', () => {
|
||||||
|
describe('@Self()', () => {
|
||||||
|
it('should return a dependency from self', () => {
|
||||||
|
const inj = Injector.create([
|
||||||
|
Engine.PROVIDER,
|
||||||
|
{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[Engine, new Self()]]}
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(inj.get(Car)).toBeAnInstanceOf(Car);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when not requested provider on self', () => {
|
||||||
|
const parent = Injector.create([Engine.PROVIDER]);
|
||||||
|
const child = Injector.create(
|
||||||
|
[{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[Engine, new Self()]]}],
|
||||||
|
parent);
|
||||||
|
|
||||||
|
expect(() => child.get(Car))
|
||||||
|
.toThrowError(`StaticInjectorError[${stringify(Car)} -> ${stringify(Engine)}]:
|
||||||
|
NullInjectorError: No provider for Engine!`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('default', () => {
|
||||||
|
it('should skip self', () => {
|
||||||
|
const parent = Injector.create([Engine.PROVIDER]);
|
||||||
|
const child = Injector.create(
|
||||||
|
[
|
||||||
|
TurboEngine.PROVIDER,
|
||||||
|
{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[SkipSelf, Engine]]}
|
||||||
|
],
|
||||||
|
parent);
|
||||||
|
|
||||||
|
expect(child.get(Car).engine).toBeAnInstanceOf(Engine);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resolve', () => {
|
||||||
|
it('should throw when mixing multi providers with regular providers', () => {
|
||||||
|
expect(() => {
|
||||||
|
Injector.create(
|
||||||
|
[{provide: Engine, useClass: BrokenEngine, deps: [], multi: true}, Engine.PROVIDER]);
|
||||||
|
}).toThrowError(/Cannot mix multi providers and regular providers/);
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
Injector.create(
|
||||||
|
[Engine.PROVIDER, {provide: Engine, useClass: BrokenEngine, deps: [], multi: true}]);
|
||||||
|
}).toThrowError(/Cannot mix multi providers and regular providers/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve forward references', () => {
|
||||||
|
const injector = Injector.create([
|
||||||
|
[{provide: forwardRef(() => BrokenEngine), useClass: forwardRef(() => Engine), deps: []}], {
|
||||||
|
provide: forwardRef(() => String),
|
||||||
|
useFactory: (e: any) => e,
|
||||||
|
deps: [forwardRef(() => BrokenEngine)]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
expect(injector.get(String)).toBeAnInstanceOf(Engine);
|
||||||
|
expect(injector.get(BrokenEngine)).toBeAnInstanceOf(Engine);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support overriding factory dependencies with dependency annotations', () => {
|
||||||
|
const injector = Injector.create([
|
||||||
|
Engine.PROVIDER,
|
||||||
|
{provide: 'token', useFactory: (e: any) => e, deps: [[new Inject(Engine)]]}
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(injector.get('token')).toBeAnInstanceOf(Engine);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('displayName', () => {
|
||||||
|
it('should work', () => {
|
||||||
|
expect(Injector.create([Engine.PROVIDER, {provide: BrokenEngine, useValue: null}]).toString())
|
||||||
|
.toEqual('StaticInjector[Injector, Engine, BrokenEngine]');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -720,7 +720,7 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||||
|
|
||||||
it('should throw when the aliased provider does not exist', () => {
|
it('should throw when the aliased provider does not exist', () => {
|
||||||
const injector = createInjector([{provide: 'car', useExisting: SportsCar}]);
|
const injector = createInjector([{provide: 'car', useExisting: SportsCar}]);
|
||||||
const e = `No provider for ${stringify(SportsCar)}!`;
|
const e = `NullInjectorError: No provider for ${stringify(SportsCar)}!`;
|
||||||
expect(() => injector.get('car')).toThrowError(e);
|
expect(() => injector.get('car')).toThrowError(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -830,7 +830,8 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||||
|
|
||||||
it('should throw when no provider defined', () => {
|
it('should throw when no provider defined', () => {
|
||||||
const injector = createInjector([]);
|
const injector = createInjector([]);
|
||||||
expect(() => injector.get('NonExisting')).toThrowError('No provider for NonExisting!');
|
expect(() => injector.get('NonExisting'))
|
||||||
|
.toThrowError('NullInjectorError: No provider for NonExisting!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when trying to instantiate a cyclic dependency', () => {
|
it('should throw when trying to instantiate a cyclic dependency', () => {
|
||||||
|
|
|
@ -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 {Inject, Injectable, InjectionToken, Optional, ReflectiveInjector} from '@angular/core';
|
import {Injectable, InjectionToken, Injector, Optional, ReflectiveInjector} from '@angular/core';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('Provider examples', () => {
|
describe('Provider examples', () => {
|
||||||
|
@ -30,8 +30,7 @@ export function main() {
|
||||||
describe('ValueProvider', () => {
|
describe('ValueProvider', () => {
|
||||||
it('works', () => {
|
it('works', () => {
|
||||||
// #docregion ValueProvider
|
// #docregion ValueProvider
|
||||||
const injector =
|
const injector = Injector.create([{provide: String, useValue: 'Hello'}]);
|
||||||
ReflectiveInjector.resolveAndCreate([{provide: String, useValue: 'Hello'}]);
|
|
||||||
|
|
||||||
expect(injector.get(String)).toEqual('Hello');
|
expect(injector.get(String)).toEqual('Hello');
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
@ -41,12 +40,13 @@ export function main() {
|
||||||
describe('MultiProviderAspect', () => {
|
describe('MultiProviderAspect', () => {
|
||||||
it('works', () => {
|
it('works', () => {
|
||||||
// #docregion MultiProviderAspect
|
// #docregion MultiProviderAspect
|
||||||
const injector = ReflectiveInjector.resolveAndCreate([
|
const locale = new InjectionToken<string[]>('locale');
|
||||||
{provide: 'local', multi: true, useValue: 'en'},
|
const injector = Injector.create([
|
||||||
{provide: 'local', multi: true, useValue: 'sk'},
|
{provide: locale, multi: true, useValue: 'en'},
|
||||||
|
{provide: locale, multi: true, useValue: 'sk'},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const locales: string[] = injector.get('local');
|
const locales: string[] = injector.get(locale);
|
||||||
expect(locales).toEqual(['en', 'sk']);
|
expect(locales).toEqual(['en', 'sk']);
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
});
|
});
|
||||||
|
@ -89,6 +89,61 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('StaticClassProvider', () => {
|
||||||
|
it('works', () => {
|
||||||
|
// #docregion StaticClassProvider
|
||||||
|
abstract class Shape { name: string; }
|
||||||
|
|
||||||
|
class Square extends Shape {
|
||||||
|
name = 'square';
|
||||||
|
}
|
||||||
|
|
||||||
|
const injector = Injector.create([{provide: Shape, useClass: Square, deps: []}]);
|
||||||
|
|
||||||
|
const shape: Shape = injector.get(Shape);
|
||||||
|
expect(shape.name).toEqual('square');
|
||||||
|
expect(shape instanceof Square).toBe(true);
|
||||||
|
// #enddocregion
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is different then useExisting', () => {
|
||||||
|
// #docregion StaticClassProviderDifference
|
||||||
|
class Greeting {
|
||||||
|
salutation = 'Hello';
|
||||||
|
}
|
||||||
|
|
||||||
|
class FormalGreeting extends Greeting {
|
||||||
|
salutation = 'Greetings';
|
||||||
|
}
|
||||||
|
|
||||||
|
const injector = Injector.create([
|
||||||
|
{provide: FormalGreeting, useClass: FormalGreeting, deps: []},
|
||||||
|
{provide: Greeting, useClass: FormalGreeting, deps: []}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// The injector returns different instances.
|
||||||
|
// See: {provide: ?, useExisting: ?} if you want the same instance.
|
||||||
|
expect(injector.get(FormalGreeting)).not.toBe(injector.get(Greeting));
|
||||||
|
// #enddocregion
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ConstructorProvider', () => {
|
||||||
|
it('works', () => {
|
||||||
|
// #docregion ConstructorProvider
|
||||||
|
class Square {
|
||||||
|
name = 'square';
|
||||||
|
}
|
||||||
|
|
||||||
|
const injector = Injector.create([{provide: Square, deps: []}]);
|
||||||
|
|
||||||
|
const shape: Square = injector.get(Square);
|
||||||
|
expect(shape.name).toEqual('square');
|
||||||
|
expect(shape instanceof Square).toBe(true);
|
||||||
|
// #enddocregion
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('ExistingProvider', () => {
|
describe('ExistingProvider', () => {
|
||||||
it('works', () => {
|
it('works', () => {
|
||||||
// #docregion ExistingProvider
|
// #docregion ExistingProvider
|
||||||
|
@ -100,8 +155,9 @@ export function main() {
|
||||||
salutation = 'Greetings';
|
salutation = 'Greetings';
|
||||||
}
|
}
|
||||||
|
|
||||||
const injector = ReflectiveInjector.resolveAndCreate(
|
const injector = Injector.create([
|
||||||
[FormalGreeting, {provide: Greeting, useExisting: FormalGreeting}]);
|
{provide: FormalGreeting, deps: []}, {provide: Greeting, useExisting: FormalGreeting}
|
||||||
|
]);
|
||||||
|
|
||||||
expect(injector.get(Greeting).salutation).toEqual('Greetings');
|
expect(injector.get(Greeting).salutation).toEqual('Greetings');
|
||||||
expect(injector.get(FormalGreeting).salutation).toEqual('Greetings');
|
expect(injector.get(FormalGreeting).salutation).toEqual('Greetings');
|
||||||
|
@ -116,7 +172,7 @@ export function main() {
|
||||||
const Location = new InjectionToken('location');
|
const Location = new InjectionToken('location');
|
||||||
const Hash = new InjectionToken('hash');
|
const Hash = new InjectionToken('hash');
|
||||||
|
|
||||||
const injector = ReflectiveInjector.resolveAndCreate([
|
const injector = Injector.create([
|
||||||
{provide: Location, useValue: 'http://angular.io/#someLocation'}, {
|
{provide: Location, useValue: 'http://angular.io/#someLocation'}, {
|
||||||
provide: Hash,
|
provide: Hash,
|
||||||
useFactory: (location: string) => location.split('#')[1],
|
useFactory: (location: string) => location.split('#')[1],
|
||||||
|
@ -133,7 +189,7 @@ export function main() {
|
||||||
const Location = new InjectionToken('location');
|
const Location = new InjectionToken('location');
|
||||||
const Hash = new InjectionToken('hash');
|
const Hash = new InjectionToken('hash');
|
||||||
|
|
||||||
const injector = ReflectiveInjector.resolveAndCreate([{
|
const injector = Injector.create([{
|
||||||
provide: Hash,
|
provide: Hash,
|
||||||
useFactory: (location: string) => `Hash for: ${location}`,
|
useFactory: (location: string) => `Hash for: ${location}`,
|
||||||
// use a nested array to define metadata for dependencies.
|
// use a nested array to define metadata for dependencies.
|
||||||
|
|
|
@ -19,7 +19,7 @@ export const platformWorkerAppDynamic =
|
||||||
createPlatformFactory(platformCoreDynamic, 'workerAppDynamic', [
|
createPlatformFactory(platformCoreDynamic, 'workerAppDynamic', [
|
||||||
{
|
{
|
||||||
provide: COMPILER_OPTIONS,
|
provide: COMPILER_OPTIONS,
|
||||||
useValue: {providers: [{provide: ResourceLoader, useClass: ResourceLoaderImpl}]},
|
useValue: {providers: [{provide: ResourceLoader, useClass: ResourceLoaderImpl, deps: []}]},
|
||||||
multi: true
|
multi: true
|
||||||
},
|
},
|
||||||
{provide: PLATFORM_ID, useValue: PLATFORM_WORKER_UI_ID}
|
{provide: PLATFORM_ID, useValue: PLATFORM_WORKER_UI_ID}
|
||||||
|
|
|
@ -491,6 +491,7 @@ export declare abstract class Injector {
|
||||||
/** @deprecated */ abstract get(token: any, notFoundValue?: any): any;
|
/** @deprecated */ abstract get(token: any, notFoundValue?: any): any;
|
||||||
static NULL: Injector;
|
static NULL: Injector;
|
||||||
static THROW_IF_NOT_FOUND: Object;
|
static THROW_IF_NOT_FOUND: Object;
|
||||||
|
static create(providers: StaticProvider[], parent?: Injector): Injector;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
|
@ -951,6 +952,9 @@ export interface SkipSelfDecorator {
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
export declare function state(name: string, styles: AnimationStyleMetadata): AnimationStateMetadata;
|
export declare function state(name: string, styles: AnimationStyleMetadata): AnimationStateMetadata;
|
||||||
|
|
||||||
|
/** @stable */
|
||||||
|
export declare type StaticProvider = ValueProvider | ExistingProvider | StaticClassProvider | ConstructorProvider | FactoryProvider | any[];
|
||||||
|
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
export declare function style(tokens: {
|
export declare function style(tokens: {
|
||||||
[key: string]: string | number;
|
[key: string]: string | number;
|
||||||
|
|
Loading…
Reference in New Issue