/// import {Map, List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import {ResolvedBinding, Binding, BindingBuilder, bind} from './binding'; import { AbstractBindingError, NoBindingError, AsyncBindingError, CyclicDependencyError, InstantiationError, InvalidBindingError } from './exceptions'; import {FunctionWrapper, Type, isPresent, isBlank} from 'angular2/src/facade/lang'; import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; import {Key} from './key'; import {resolveForwardRef} from './forward_ref'; var _constructing = new Object(); var _notFound = new Object(); class _Waiting { promise: Promise; constructor(promise: Promise) { this.promise = promise; } } function _isWaiting(obj): boolean { return obj instanceof _Waiting; } /** * 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: * * 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) { * } * } * * ``` * * 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. * * ```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); * } * ``` * Notice that we don't use the `new` operator because we explicitly want to have the `Injector` * resolve all of the object's dependencies automatically. * * @exportedAs angular2/di */ export class Injector { private _bindings: List; private _instances: List; private _parent: Injector; private _defaultBindings: boolean; private _asyncStrategy: _AsyncInjectorStrategy; private _syncStrategy: _SyncInjectorStrategy; /** * 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 {@link ResolvedBinding}s. The resolution can be cached by `resolve` * for the {@link Injector} for performance-sensitive code. * * @param `bindings` can be a list of `Type`, {@link Binding}, {@link ResolvedBinding}, or a * recursive list of more bindings. * * The returned list is sparse, indexed by `id` for the {@link Key}. It is generally not useful to *application code * other than for passing it to {@link Injector} functions that require resolved binding lists, *such as * `fromResolvedBindings` and `createChildFromResolved`. */ static resolve(bindings: List): List { 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 corresponding `fromResolvedBindings` because it needs to resolve bindings first. See *`resolve` * for the {@link Injector}. * * Prefer `fromResolvedBindings` in performance-critical code that creates lots of injectors. * * @param `bindings` can be a list of `Type`, {@link Binding}, {@link ResolvedBinding}, or a *recursive list of more * bindings. * @param `defaultBindings` Setting to true will auto-create bindings. */ static resolveAndCreate(bindings: List, {defaultBindings = false}: any = {}): Injector { return new Injector(Injector.resolve(bindings), null, defaultBindings); } /** * Creates an injector from previously resolved bindings. This bypasses resolution and flattening. * This API is the recommended way to construct injectors in performance-sensitive parts. * * @param `bindings` A sparse list of {@link ResolvedBinding}s. See `resolve` for the {@link *Injector}. * @param `defaultBindings` Setting to true will auto-create bindings. */ static fromResolvedBindings(bindings: List, {defaultBindings = false}: any = {}): Injector { return new Injector(bindings, null, defaultBindings); } /** * @param `bindings` A sparse list of {@link ResolvedBinding}s. See `resolve` for the {@link * Injector}. * @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, 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); } /** * Direct parent of this injector. */ get parent(): Injector { return this._parent; } /** * Retrieves an instance from the injector. * * @param `token`: usually the `Type` of an object. (Same as the token used while setting up a *binding). * @returns an instance represented by the token. Throws if not found. */ get(token) { return this._getByKey(Key.get(token), false, false, false); } /** * Retrieves an instance from the injector. * * @param `token`: usually a `Type`. (Same as the token used while setting up a binding). * @returns an instance represented by the token. Returns `null` if not found. */ getOptional(token) { return this._getByKey(Key.get(token), false, false, true); } /** * 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). * @returns a `Promise` which resolves to the instance represented by the token. */ asyncGet(token): Promise { return this._getByKey(Key.get(token), true, false, false); } /** * 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 {@link ResolvedBinding}s. The resolution can be cached by `resolve` * for the {@link Injector} for performance-sensitive code. * * @param `bindings` can be a list of `Type`, {@link Binding}, {@link ResolvedBinding}, or a * recursive list of more bindings. * */ resolveAndCreateChild(bindings: List): Injector { return new Injector(Injector.resolve(bindings), this, false); } /** * Creates a child injector and loads a new set of {@link ResolvedBinding}s into it. * * @param `bindings`: A sparse list of {@link ResolvedBinding}s. * See `resolve` for the {@link Injector}. * @returns a new child {@link Injector}. */ createChildFromResolved(bindings: List): Injector { return new Injector(bindings, this, false); } _createInstances(): List { return ListWrapper.createFixedSize(Key.numberOfKeys + 1); } _getByKey(key: Key, returnPromise: boolean, returnLazy: boolean, optional: boolean) { if (returnLazy) { return () => this._getByKey(key, returnPromise, false, optional); } 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 NoBindingError(key); } } _resolveDependencies(key: Key, binding: ResolvedBinding, forceAsync: boolean): List { try { var getDependency = d => this._getByKey(d.key, forceAsync || d.asPromise, d.lazy, d.optional); return ListWrapper.map(binding.dependencies, getDependency); } catch (e) { this._clear(key); if (e instanceof AbstractBindingError) e.addKey(key); throw e; } } _getInstance(key: Key) { if (this._instances.length <= key.id) return null; return ListWrapper.get(this._instances, key.id); } _setInstance(key: Key, obj): void { ListWrapper.set(this._instances, key.id, obj); } _getBinding(key: Key) { var binding = this._bindings.length <= key.id ? null : ListWrapper.get(this._bindings, key.id); if (isBlank(binding) && this._defaultBindings) { var token: any = key.token; return bind(key.token).toClass(token).resolve(); } else { return binding; } } _markAsConstructing(key: Key): void { this._setInstance(key, _constructing); } _clear(key: Key): void { 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; if (binding.providedAsPromise) throw new AsyncBindingError(key); // add a marker so we can detect cyclic dependencies this.injector._markAsConstructing(key); 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) { return PromiseWrapper.resolve(this.injector); } var instance = this.injector._getInstance(key); if (instance === _constructing) { throw new CyclicDependencyError(key); } else if (_isWaiting(instance)) { return instance.promise; } else if (isPresent(instance)) { return PromiseWrapper.resolve(instance); } else { return _notFound; } } instantiate(key: Key) /* Promise?? */ { var binding = this.injector._getBinding(key); if (isBlank(binding)) return _notFound; // add a marker so we can detect cyclic dependencies this.injector._markAsConstructing(key); var deps = this.injector._resolveDependencies(key, binding, true); var depsPromise = PromiseWrapper.all(deps); var promise = PromiseWrapper.then(depsPromise, null, (e, s) => this._errorHandler(key, e, s)) .then(deps => this._findOrCreate(key, binding, deps)) .then(instance => this._cacheInstance(key, instance)); this.injector._setInstance(key, new _Waiting(promise)); return promise; } _errorHandler(key: Key, e, stack): Promise { if (e instanceof AbstractBindingError) e.addKey(key); return PromiseWrapper.reject(e, stack); } _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); throw new InstantiationError(e, key); } } _cacheInstance(key, instance) { this.injector._setInstance(key, instance); return instance } } export function resolveBindings(bindings: List): List { var resolvedList = ListWrapper.createFixedSize(bindings.length); for (var i = 0; i < bindings.length; i++) { var unresolved = resolveForwardRef(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) { 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 flattenBindings(bindings: List): List { var map = _flattenBindings(bindings, MapWrapper.create()); var res = ListWrapper.create(); MapWrapper.forEach(map, (binding, keyId) => ListWrapper.push(res, binding)); return res; } 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): Map { 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; }