406 lines
13 KiB
JavaScript
Raw Normal View History

import {Map, List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {ResolvedBinding, Binding, BindingBuilder, bind} from './binding';
import {ProviderError, NoProviderError, AsyncBindingError, CyclicDependencyError,
InstantiationError, InvalidBindingError} from './exceptions';
import {FunctionWrapper, Type, isPresent, isBlank} from 'angular2/src/facade/lang';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {Key} from './key';
var _constructing = new Object();
var _notFound = new Object();
class _Waiting {
promise:Promise;
2014-10-10 15:44:56 -04:00
constructor(promise:Promise) {
this.promise = promise;
}
}
function _isWaiting(obj):boolean {
return obj instanceof _Waiting;
}
2015-04-15 22:35:38 +00:00
/**
* A dependency injection container used for resolving dependencies.
*
* An `Injector` is a replacement for a `new` operator, which can automatically resolve the constructor dependencies.
* In typical use, application code asks for the dependencies in the constructor and they are resolved by the
* `Injector`.
*
* ## Example:
2015-04-17 03:29:05 -07:00
*
2015-04-15 22:35:38 +00:00
* Suppose that we want to inject an `Engine` into class `Car`, we would define it like this:
*
* ```javascript
* class Engine {
* }
*
* class Car {
* constructor(@Inject(Engine) engine) {
* }
* }
*
* ```
2015-04-17 03:29:05 -07:00
*
* Next we need to write the code that creates and instantiates the `Injector`. We then ask for the `root` object,
* `Car`, so that the `Injector` can recursively build all of that object's dependencies.
2015-04-15 22:35:38 +00:00
*
* ```javascript
* main() {
* var injector = Injector.resolveAndCreate([Car, Engine]);
*
* // Get a reference to the `root` object, which will recursively instantiate the tree.
* var car = injector.get(Car);
* }
* ```
2015-04-17 03:29:05 -07:00
* Notice that we don't use the `new` operator because we explicitly want to have the `Injector` resolve all of the
2015-04-15 22:35:38 +00:00
* object's dependencies automatically.
*
* @exportedAs angular2/di
*/
export class Injector {
_bindings:List;
_instances:List;
_parent:Injector;
_defaultBindings:boolean;
_asyncStrategy: _AsyncInjectorStrategy;
_syncStrategy:_SyncInjectorStrategy;
/**
2015-04-17 03:29:05 -07:00
* Turns a list of binding definitions into an internal resolved list of resolved bindings.
*
* A resolution is a process of flattening multiple nested lists and converting individual bindings into a
* list of [ResolvedBinding]s. The resolution can be cached by [Injector.resolve] for performance-sensitive
* code.
*
2015-04-15 22:35:38 +00:00
* @param [bindings] can be a list of [Type], [Binding], [ResolvedBinding], or a recursive list of more bindings.
*
* The returned list is sparse, indexed by [Key.id]. It is generally not useful to application code other than for
* passing it to [Injector] functions that require resolved binding lists, such as [fromResolvedBindings] and
* [createChildFromResolved].
*/
static resolve(bindings:List/*<ResolvedBinding|Binding|Type|List>*/):List<ResolvedBinding> {
var resolvedBindings = _resolveBindings(bindings);
var flatten = _flattenBindings(resolvedBindings, MapWrapper.create());
return _createListOfBindings(flatten);
}
/**
* Resolves bindings and creates an injector based on those bindings. This function is slower than the
2015-04-15 22:35:38 +00:00
* corresponding [fromResolvedBindings] because it needs to resolve bindings first. See [Injector.resolve].
2015-04-17 03:29:05 -07:00
*
2015-04-15 22:35:38 +00:00
* Prefer [fromResolvedBindings] in performance-critical code that creates lots of injectors.
*
* @param [bindings] can be a list of [Type], [Binding], [ResolvedBinding], or a recursive list of more bindings.
* @param [defaultBindings] Setting to true will auto-create bindings.
*/
static resolveAndCreate(bindings:List/*<ResolvedBinding|Binding|Type|List>*/, {defaultBindings=false}={}) {
return new Injector(Injector.resolve(bindings), null, defaultBindings);
}
/**
2015-04-17 03:29:05 -07:00
* Creates an injector from previously resolved bindings. This bypasses resolution and flattening. This API is the
2015-04-15 22:35:38 +00:00
* recommended way to construct injectors in performance-sensitive parts.
*
* @param [bindings] A sparse list of [ResolvedBinding]s. See [Injector.resolve].
* @param [defaultBindings] Setting to true will auto-create bindings.
*/
static fromResolvedBindings(bindings:List<ResolvedBinding>, {defaultBindings=false}={}) {
return new Injector(bindings, null, defaultBindings);
}
2015-04-15 22:35:38 +00:00
/**
* @param [bindings] A sparse list of [ResolvedBinding]s. See [Injector.resolve].
* @param [parent] Parent Injector or `null` if root injector.
* @param [defaultBindings] Setting to true will auto-create bindings. (Only use with root injector.)
*/
constructor(bindings:List<ResolvedBinding>, parent:Injector, defaultBindings:boolean) {
this._bindings = bindings;
this._instances = this._createInstances();
this._parent = parent;
this._defaultBindings = defaultBindings;
this._asyncStrategy = new _AsyncInjectorStrategy(this);
this._syncStrategy = new _SyncInjectorStrategy(this);
}
2015-04-15 22:35:38 +00:00
/**
2015-04-17 03:29:05 -07:00
* Retrieves an instance from the injector.
*
* @param [token] usually the [Type] of an object. (Same as the token used while setting up a binding).
2015-04-15 22:35:38 +00:00
* @returns an instance represented by the token. Throws if not found.
*/
get(token) {
2015-04-17 03:29:05 -07:00
return this._getByKey(Key.get(token), false, false, false);
}
2015-04-15 22:35:38 +00:00
/**
2015-04-17 03:29:05 -07:00
* Retrieves an instance from the injector.
*
* @param [token] usually a [Type]. (Same as the token used while setting up a binding).
2015-04-15 22:35:38 +00:00
* @returns an instance represented by the token. Returns `null` if not found.
*/
getOptional(token) {
return this._getByKey(Key.get(token), false, false, true);
}
2015-04-15 22:35:38 +00:00
/**
2015-04-17 03:29:05 -07:00
* Retrieves an instance from the injector asynchronously. Used with asynchronous bindings.
*
* @param [token] usually a [Type]. (Same as token used while setting up a binding).
2015-04-15 22:35:38 +00:00
* @returns a [Promise] which resolves to the instance represented by the token.
*/
asyncGet(token):Promise {
return this._getByKey(Key.get(token), true, false, false);
}
2015-04-15 22:35:38 +00:00
/**
2015-04-17 03:29:05 -07:00
* Creates a child injector and loads a new set of bindings into it.
*
* A resolution is a process of flattening multiple nested lists and converting individual bindings into a
* list of [ResolvedBinding]s. The resolution can be cached by [Injector.resolve] for performance-sensitive
2015-04-15 22:35:38 +00:00
* code.
2015-04-17 03:29:05 -07:00
*
2015-04-15 22:35:38 +00:00
* See: [Injector.resolve].
2015-04-17 03:29:05 -07:00
*
2015-04-15 22:35:38 +00:00
* @param [bindings] can be a list of [Type], [Binding], [ResolvedBinding], or a recursive list of more bindings.
* @returns a new child `Injector`.
*/
resolveAndCreateChild(bindings:List/*<ResolvedBinding|Binding|Type|List>*/):Injector {
return new Injector(Injector.resolve(bindings), this, false);
}
2015-04-15 22:35:38 +00:00
/**
2015-04-17 03:29:05 -07:00
* Creates a child injector and loads a new set of [ResolvedBinding]s into it.
*
2015-04-15 22:35:38 +00:00
* @param [bindings] A sparse list of [ResolvedBinding]s. See [Injector.resolve].
* @returns a new child `Injector`.
*/
createChildFromResolved(bindings:List<ResolvedBinding>):Injector {
return new Injector(bindings, this, false);
}
_createInstances():List {
2014-10-20 15:17:06 -04:00
return ListWrapper.createFixedSize(Key.numberOfKeys + 1);
}
_getByKey(key:Key, returnPromise:boolean, returnLazy:boolean, optional:boolean) {
2014-10-06 13:45:24 -04:00
if (returnLazy) {
return () => this._getByKey(key, returnPromise, false, optional);
2014-10-06 13:45:24 -04:00
}
2014-10-10 15:44:56 -04:00
var strategy = returnPromise ? this._asyncStrategy : this._syncStrategy;
var instance = strategy.readFromCache(key);
if (instance !== _notFound) return instance;
instance = strategy.instantiate(key);
if (instance !== _notFound) return instance;
if (isPresent(this._parent)) {
return this._parent._getByKey(key, returnPromise, returnLazy, optional);
}
if (optional) {
return null;
} else {
throw new NoProviderError(key);
}
}
_resolveDependencies(key:Key, binding:ResolvedBinding, forceAsync:boolean):List {
2014-10-09 11:08:00 -04:00
try {
var getDependency = d => this._getByKey(d.key, forceAsync || d.asPromise, d.lazy, d.optional);
2014-10-09 11:08:00 -04:00
return ListWrapper.map(binding.dependencies, getDependency);
} catch (e) {
this._clear(key);
if (e instanceof ProviderError) e.addKey(key);
throw e;
}
}
2014-10-07 10:34:07 -04:00
_getInstance(key:Key) {
if (this._instances.length <= key.id) return null;
return ListWrapper.get(this._instances, key.id);
}
2014-10-07 10:34:07 -04:00
_setInstance(key:Key, obj) {
ListWrapper.set(this._instances, key.id, obj);
}
2014-10-07 10:34:07 -04:00
_getBinding(key:Key) {
var binding = this._bindings.length <= key.id ?
null :
ListWrapper.get(this._bindings, key.id);
if (isBlank(binding) && this._defaultBindings) {
return bind(key.token).toClass(key.token).resolve();
} else {
return binding;
}
}
_markAsConstructing(key:Key) {
this._setInstance(key, _constructing);
}
_clear(key:Key) {
this._setInstance(key, null);
}
}
class _SyncInjectorStrategy {
injector:Injector;
constructor(injector:Injector) {
this.injector = injector;
}
readFromCache(key:Key) {
if (key.token === Injector) {
return this.injector;
}
var instance = this.injector._getInstance(key);
if (instance === _constructing) {
throw new CyclicDependencyError(key);
} else if (isPresent(instance) && !_isWaiting(instance)) {
return instance;
} else {
return _notFound;
}
}
instantiate(key:Key) {
var binding = this.injector._getBinding(key);
if (isBlank(binding)) return _notFound;
2014-10-10 15:44:56 -04:00
if (binding.providedAsPromise) throw new AsyncBindingError(key);
//add a marker so we can detect cyclic dependencies
this.injector._markAsConstructing(key);
2014-10-09 11:08:00 -04:00
var deps = this.injector._resolveDependencies(key, binding, false);
return this._createInstance(key, binding, deps);
}
_createInstance(key:Key, binding:ResolvedBinding, deps:List) {
try {
var instance = FunctionWrapper.apply(binding.factory, deps);
this.injector._setInstance(key, instance);
return instance;
} catch (e) {
this.injector._clear(key);
throw new InstantiationError(e, key);
}
}
}
class _AsyncInjectorStrategy {
injector:Injector;
constructor(injector:Injector) {
this.injector = injector;
}
readFromCache(key:Key) {
if (key.token === Injector) {
2014-10-10 15:44:56 -04:00
return PromiseWrapper.resolve(this.injector);
}
var instance = this.injector._getInstance(key);
if (instance === _constructing) {
throw new CyclicDependencyError(key);
} else if (_isWaiting(instance)) {
2014-10-10 15:44:56 -04:00
return instance.promise;
} else if (isPresent(instance)) {
2014-10-10 15:44:56 -04:00
return PromiseWrapper.resolve(instance);
} else {
return _notFound;
}
}
instantiate(key:Key) {
var binding = this.injector._getBinding(key);
if (isBlank(binding)) return _notFound;
//add a marker so we can detect cyclic dependencies
this.injector._markAsConstructing(key);
2014-10-09 11:08:00 -04:00
var deps = this.injector._resolveDependencies(key, binding, true);
2014-10-10 15:44:56 -04:00
var depsPromise = PromiseWrapper.all(deps);
2014-12-01 20:06:21 +01:00
var promise = PromiseWrapper
.then(depsPromise, null, (e) => this._errorHandler(key, e))
.then(deps => this._findOrCreate(key, binding, deps))
.then(instance => this._cacheInstance(key, instance));
2014-10-10 15:44:56 -04:00
this.injector._setInstance(key, new _Waiting(promise));
return promise;
}
2014-10-10 15:44:56 -04:00
_errorHandler(key:Key, e):Promise {
if (e instanceof ProviderError) e.addKey(key);
2014-10-10 15:44:56 -04:00
return PromiseWrapper.reject(e);
}
_findOrCreate(key:Key, binding:ResolvedBinding, deps:List) {
try {
var instance = this.injector._getInstance(key);
if (!_isWaiting(instance)) return instance;
return FunctionWrapper.apply(binding.factory, deps);
} catch (e) {
this.injector._clear(key);
2014-10-07 10:34:07 -04:00
throw new InstantiationError(e, key);
}
}
_cacheInstance(key, instance) {
this.injector._setInstance(key, instance);
return instance
}
}
function _resolveBindings(bindings:List): List {
var resolvedList = ListWrapper.createFixedSize(bindings.length);
for (var i = 0; i < bindings.length; i++) {
var unresolved = bindings[i];
var resolved;
if (unresolved instanceof ResolvedBinding) {
resolved = unresolved; // ha-ha! I'm easily amused
} else if (unresolved instanceof Type) {
resolved = bind(unresolved).toClass(unresolved).resolve();
} else if (unresolved instanceof Binding) {
2015-04-15 22:35:38 +00:00
resolved = unresolved.resolve();
} else if (unresolved instanceof List) {
resolved = _resolveBindings(unresolved);
} else if (unresolved instanceof BindingBuilder) {
throw new InvalidBindingError(unresolved.token);
} else {
throw new InvalidBindingError(unresolved);
}
resolvedList[i] = resolved;
}
return resolvedList;
}
function _createListOfBindings(flattenedBindings):List {
var bindings = ListWrapper.createFixedSize(Key.numberOfKeys + 1);
MapWrapper.forEach(flattenedBindings, (v, keyId) => bindings[keyId] = v);
return bindings;
}
function _flattenBindings(bindings:List, res:Map) {
2014-10-07 10:34:07 -04:00
ListWrapper.forEach(bindings, function (b) {
if (b instanceof ResolvedBinding) {
MapWrapper.set(res, b.key.id, b);
} else if (b instanceof List) {
_flattenBindings(b, res);
}
});
return res;
}