Skip to content

Commit 1eea2b2

Browse files
committed
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 angular#1891
1 parent 0e04467 commit 1eea2b2

File tree

14 files changed

+225
-13
lines changed

14 files changed

+225
-13
lines changed

modules/angular2/angular2.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from './core';
33
export * from './annotations';
44
export * from './directives';
55
export * from './forms';
6+
export * from './di';
67
export {Observable, EventEmitter} from 'angular2/src/facade/async';
78
export * from 'angular2/src/render/api';
89
export {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';

modules/angular2/di.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
export * from './src/di/annotations';
99
export * from './src/di/decorators';
10+
export * from './src/di/forward_ref';
1011
export {Injector} from './src/di/injector';
1112
export {Binding, ResolvedBinding, Dependency, bind} from './src/di/binding';
1213
export {Key, KeyRegistry, TypeLiteral} from './src/di/key';

modules/angular2/src/core/compiler/compiler.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Binding} from 'angular2/di';
1+
import {Binding, resolveForwardRef} from 'angular2/di';
22
import {Injectable} from 'angular2/src/di/annotations_impl';
33
import {Type, isBlank, isPresent, BaseException, normalizeBlank, stringify} from 'angular2/src/facade/lang';
44
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
@@ -237,7 +237,7 @@ export class Compiler {
237237

238238
_flattenList(tree:List<any>, out:List<any> /*<Type|Binding>*/):void {
239239
for (var i = 0; i < tree.length; i++) {
240-
var item = tree[i];
240+
var item = resolveForwardRef(tree[i]);
241241
if (ListWrapper.isList(item)) {
242242
this._flattenList(item, out);
243243
} else {

modules/angular2/src/core/compiler/directive_resolver.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import {Injectable} from 'angular2/src/di/annotations_impl';
2+
import {resolveForwardRef} from 'angular2/di';
23
import {Type, isPresent, BaseException, stringify} from 'angular2/src/facade/lang';
34
import {Directive} from '../annotations_impl/annotations';
45
import {reflector} from 'angular2/src/reflection/reflection';
56

67
@Injectable()
78
export class DirectiveResolver {
89
resolve(type:Type):Directive {
9-
var annotations = reflector.annotations(type);
10+
var annotations = reflector.annotations(resolveForwardRef(type));
1011
if (isPresent(annotations)) {
1112
for (var i=0; i<annotations.length; i++) {
1213
var annotation = annotations[i];

modules/angular2/src/core/compiler/element_injector.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
33
import {Math} from 'angular2/src/facade/math';
44
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
55
import {Injector, Key, Dependency, bind, Binding, ResolvedBinding, NoBindingError,
6-
AbstractBindingError, CyclicDependencyError} from 'angular2/di';
6+
AbstractBindingError, CyclicDependencyError, resolveForwardRef} from 'angular2/di';
77
import {Parent, Ancestor} from 'angular2/src/core/annotations_impl/visibility';
88
import {Attribute, Query} from 'angular2/src/core/annotations_impl/di';
99
import * as viewModule from './view';
@@ -220,7 +220,7 @@ export class DirectiveDependency extends Dependency {
220220

221221
static _query(properties) {
222222
var p = ListWrapper.find(properties, (p) => p instanceof Query);
223-
return isPresent(p) ? p.directive : null;
223+
return isPresent(p) ? resolveForwardRef(p.directive) : null;
224224
}
225225
}
226226

modules/angular2/src/di/binding.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
DependencyAnnotation
1111
} from './annotations_impl';
1212
import {NoAnnotationError} from './exceptions';
13+
import {resolveForwardRef} from './forward_ref';
1314

1415
/**
1516
* @private
@@ -217,8 +218,9 @@ export class Binding {
217218
var resolvedDeps;
218219
var isAsync = false;
219220
if (isPresent(this.toClass)) {
220-
factoryFn = reflector.factory(this.toClass);
221-
resolvedDeps = _dependenciesFor(this.toClass);
221+
var toClass = resolveForwardRef(this.toClass);
222+
factoryFn = reflector.factory(toClass);
223+
resolvedDeps = _dependenciesFor(toClass);
222224
} else if (isPresent(this.toAlias)) {
223225
factoryFn = (aliasInstance) => aliasInstance;
224226
resolvedDeps = [Dependency.fromKey(Key.get(this.toAlias))];
@@ -234,7 +236,8 @@ export class Binding {
234236
resolvedDeps = _EMPTY_LIST;
235237
}
236238

237-
return new ResolvedBinding(Key.get(this.token), factoryFn, resolvedDeps, isAsync);
239+
return new ResolvedBinding(Key.get(resolveForwardRef(this.token)), factoryFn, resolvedDeps,
240+
isAsync);
238241
}
239242
}
240243

@@ -428,7 +431,8 @@ export class BindingBuilder {
428431
function _constructDependencies(factoryFunction: Function, dependencies: List<any>) {
429432
return isBlank(dependencies) ?
430433
_dependenciesFor(factoryFunction) :
431-
ListWrapper.map(dependencies, (t) => Dependency.fromKey(Key.get(t)));
434+
ListWrapper.map(dependencies,
435+
(t) => Dependency.fromKey(Key.get(resolveForwardRef(t))));
432436
}
433437

434438
function _dependenciesFor(typeOrFunc): List<any> {
@@ -475,6 +479,8 @@ function _extractToken(typeOrFunc, annotations) {
475479
}
476480
}
477481

482+
token = resolveForwardRef(token);
483+
478484
if (isPresent(token)) {
479485
return _createDependency(token, asPromise, lazy, optional, depProps);
480486
} else {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
library angular2.di.forward_ref;
2+
3+
typedef Type ForwardRefFn();
4+
5+
/**
6+
* Dart does not have the forward ref problem, so this function is a noop.
7+
*/
8+
forwardRef(ForwardRefFn forwardRefFn) => forwardRefFn();
9+
10+
/**
11+
* Lazily retrieve the reference value.
12+
*
13+
* See: {@link forwardRef}
14+
*
15+
* @exportedAs angular2/di
16+
*/
17+
resolveForwardRef(type) => type;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import {Type} from 'angular2/src/facade/lang';
2+
3+
export interface ForwardRefFn { (): Type; }
4+
5+
/**
6+
* Allows to refer to references which are not yet defined.
7+
*
8+
* This situation arises when the key which we need te refer to for the purposes of DI is declared,
9+
* but not yet defined.
10+
*
11+
* ## Example:
12+
*
13+
* ```
14+
* class Door {
15+
* // Incorrect way to refer to a reference which is defined later.
16+
* // This fails because `Lock` is undefined at this point.
17+
* constructor(lock:Lock) { }
18+
*
19+
* // Correct way to refer to a reference which is defined later.
20+
* // The reference needs to be captured in a closure.
21+
* constructor(@Inject(forwardRef(() => Lock)) lock:Lock) { }
22+
* }
23+
*
24+
* // Only at this point the lock is defined.
25+
* class Lock {
26+
* }
27+
* ```
28+
*
29+
* @exportedAs angular2/di
30+
*/
31+
export function forwardRef(forwardRefFn: ForwardRefFn): Type {
32+
(<any>forwardRefFn).__forward_ref__ = forwardRef;
33+
return (<Type><any>forwardRefFn);
34+
}
35+
36+
/**
37+
* Lazily retrieve the reference value.
38+
*
39+
* See: {@link forwardRef}
40+
*
41+
* @exportedAs angular2/di
42+
*/
43+
export function resolveForwardRef(type: any): any {
44+
if (typeof type == 'function' && type.hasOwnProperty('__forward_ref__') &&
45+
type.__forward_ref__ === forwardRef) {
46+
return (<ForwardRefFn>type)();
47+
} else {
48+
return type;
49+
}
50+
}

modules/angular2/src/di/injector.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import {FunctionWrapper, Type, isPresent, isBlank} from 'angular2/src/facade/lang';
1414
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
1515
import {Key} from './key';
16+
import {resolveForwardRef} from './forward_ref';
1617

1718
var _constructing = new Object();
1819
var _notFound = new Object();
@@ -370,7 +371,7 @@ class _AsyncInjectorStrategy {
370371
function _resolveBindings(bindings: List<any>): List<ResolvedBinding> {
371372
var resolvedList = ListWrapper.createFixedSize(bindings.length);
372373
for (var i = 0; i < bindings.length; i++) {
373-
var unresolved = bindings[i];
374+
var unresolved = resolveForwardRef(bindings[i]);
374375
var resolved;
375376
if (unresolved instanceof ResolvedBinding) {
376377
resolved = unresolved; // ha-ha! I'm easily amused

modules/angular2/src/di/key.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {MapWrapper} from 'angular2/src/facade/collection';
2-
import {stringify, CONST, Type} from 'angular2/src/facade/lang';
2+
import {stringify, CONST, Type, isBlank, BaseException} from 'angular2/src/facade/lang';
33
import {TypeLiteral} from './type_literal';
44

55
export {TypeLiteral} from './type_literal';
@@ -26,6 +26,9 @@ export class Key {
2626
* @private
2727
*/
2828
constructor(token: Object, id: number) {
29+
if (isBlank(token)) {
30+
throw new BaseException('Token must be defined!');
31+
}
2932
this.token = token;
3033
this.id = id;
3134
}

0 commit comments

Comments
 (0)