feat: allow for forward references in injection

It is possible for a class defined first to be referencing a class defined later,
and as a result at the time of the definition it is not possible to access the later's
class reference. This allows to refer to the later defined class through
a closure.Closes #1891
This commit is contained in:
Misko Hevery
2015-05-13 15:54:46 -07:00
parent 0e04467b8a
commit 1eea2b254e
14 changed files with 225 additions and 13 deletions

View File

@ -1,4 +1,4 @@
import {Binding} from 'angular2/di';
import {Binding, resolveForwardRef} from 'angular2/di';
import {Injectable} from 'angular2/src/di/annotations_impl';
import {Type, isBlank, isPresent, BaseException, normalizeBlank, stringify} from 'angular2/src/facade/lang';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
@ -237,7 +237,7 @@ export class Compiler {
_flattenList(tree:List<any>, out:List<any> /*<Type|Binding>*/):void {
for (var i = 0; i < tree.length; i++) {
var item = tree[i];
var item = resolveForwardRef(tree[i]);
if (ListWrapper.isList(item)) {
this._flattenList(item, out);
} else {

View File

@ -1,4 +1,5 @@
import {Injectable} from 'angular2/src/di/annotations_impl';
import {resolveForwardRef} from 'angular2/di';
import {Type, isPresent, BaseException, stringify} from 'angular2/src/facade/lang';
import {Directive} from '../annotations_impl/annotations';
import {reflector} from 'angular2/src/reflection/reflection';
@ -6,7 +7,7 @@ import {reflector} from 'angular2/src/reflection/reflection';
@Injectable()
export class DirectiveResolver {
resolve(type:Type):Directive {
var annotations = reflector.annotations(type);
var annotations = reflector.annotations(resolveForwardRef(type));
if (isPresent(annotations)) {
for (var i=0; i<annotations.length; i++) {
var annotation = annotations[i];

View File

@ -3,7 +3,7 @@ import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
import {Math} from 'angular2/src/facade/math';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {Injector, Key, Dependency, bind, Binding, ResolvedBinding, NoBindingError,
AbstractBindingError, CyclicDependencyError} from 'angular2/di';
AbstractBindingError, CyclicDependencyError, resolveForwardRef} from 'angular2/di';
import {Parent, Ancestor} from 'angular2/src/core/annotations_impl/visibility';
import {Attribute, Query} from 'angular2/src/core/annotations_impl/di';
import * as viewModule from './view';
@ -220,7 +220,7 @@ export class DirectiveDependency extends Dependency {
static _query(properties) {
var p = ListWrapper.find(properties, (p) => p instanceof Query);
return isPresent(p) ? p.directive : null;
return isPresent(p) ? resolveForwardRef(p.directive) : null;
}
}

View File

@ -10,6 +10,7 @@ import {
DependencyAnnotation
} from './annotations_impl';
import {NoAnnotationError} from './exceptions';
import {resolveForwardRef} from './forward_ref';
/**
* @private
@ -217,8 +218,9 @@ export class Binding {
var resolvedDeps;
var isAsync = false;
if (isPresent(this.toClass)) {
factoryFn = reflector.factory(this.toClass);
resolvedDeps = _dependenciesFor(this.toClass);
var toClass = resolveForwardRef(this.toClass);
factoryFn = reflector.factory(toClass);
resolvedDeps = _dependenciesFor(toClass);
} else if (isPresent(this.toAlias)) {
factoryFn = (aliasInstance) => aliasInstance;
resolvedDeps = [Dependency.fromKey(Key.get(this.toAlias))];
@ -234,7 +236,8 @@ export class Binding {
resolvedDeps = _EMPTY_LIST;
}
return new ResolvedBinding(Key.get(this.token), factoryFn, resolvedDeps, isAsync);
return new ResolvedBinding(Key.get(resolveForwardRef(this.token)), factoryFn, resolvedDeps,
isAsync);
}
}
@ -428,7 +431,8 @@ export class BindingBuilder {
function _constructDependencies(factoryFunction: Function, dependencies: List<any>) {
return isBlank(dependencies) ?
_dependenciesFor(factoryFunction) :
ListWrapper.map(dependencies, (t) => Dependency.fromKey(Key.get(t)));
ListWrapper.map(dependencies,
(t) => Dependency.fromKey(Key.get(resolveForwardRef(t))));
}
function _dependenciesFor(typeOrFunc): List<any> {
@ -475,6 +479,8 @@ function _extractToken(typeOrFunc, annotations) {
}
}
token = resolveForwardRef(token);
if (isPresent(token)) {
return _createDependency(token, asPromise, lazy, optional, depProps);
} else {

View File

@ -0,0 +1,17 @@
library angular2.di.forward_ref;
typedef Type ForwardRefFn();
/**
* Dart does not have the forward ref problem, so this function is a noop.
*/
forwardRef(ForwardRefFn forwardRefFn) => forwardRefFn();
/**
* Lazily retrieve the reference value.
*
* See: {@link forwardRef}
*
* @exportedAs angular2/di
*/
resolveForwardRef(type) => type;

View File

@ -0,0 +1,50 @@
import {Type} from 'angular2/src/facade/lang';
export interface ForwardRefFn { (): Type; }
/**
* Allows to refer to references which are not yet defined.
*
* This situation arises when the key which we need te refer to for the purposes of DI is declared,
* but not yet defined.
*
* ## Example:
*
* ```
* class Door {
* // Incorrect way to refer to a reference which is defined later.
* // This fails because `Lock` is undefined at this point.
* constructor(lock:Lock) { }
*
* // Correct way to refer to a reference which is defined later.
* // The reference needs to be captured in a closure.
* constructor(@Inject(forwardRef(() => Lock)) lock:Lock) { }
* }
*
* // Only at this point the lock is defined.
* class Lock {
* }
* ```
*
* @exportedAs angular2/di
*/
export function forwardRef(forwardRefFn: ForwardRefFn): Type {
(<any>forwardRefFn).__forward_ref__ = forwardRef;
return (<Type><any>forwardRefFn);
}
/**
* Lazily retrieve the reference value.
*
* See: {@link forwardRef}
*
* @exportedAs angular2/di
*/
export function resolveForwardRef(type: any): any {
if (typeof type == 'function' && type.hasOwnProperty('__forward_ref__') &&
type.__forward_ref__ === forwardRef) {
return (<ForwardRefFn>type)();
} else {
return type;
}
}

View File

@ -13,6 +13,7 @@ import {
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();
@ -370,7 +371,7 @@ class _AsyncInjectorStrategy {
function _resolveBindings(bindings: List<any>): List<ResolvedBinding> {
var resolvedList = ListWrapper.createFixedSize(bindings.length);
for (var i = 0; i < bindings.length; i++) {
var unresolved = bindings[i];
var unresolved = resolveForwardRef(bindings[i]);
var resolved;
if (unresolved instanceof ResolvedBinding) {
resolved = unresolved; // ha-ha! I'm easily amused

View File

@ -1,5 +1,5 @@
import {MapWrapper} from 'angular2/src/facade/collection';
import {stringify, CONST, Type} from 'angular2/src/facade/lang';
import {stringify, CONST, Type, isBlank, BaseException} from 'angular2/src/facade/lang';
import {TypeLiteral} from './type_literal';
export {TypeLiteral} from './type_literal';
@ -26,6 +26,9 @@ export class Key {
* @private
*/
constructor(token: Object, id: number) {
if (isBlank(token)) {
throw new BaseException('Token must be defined!');
}
this.token = token;
this.id = id;
}