Skip to content

Commit

Permalink
fix(di): refactor bindings to support Dart annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
yjbanov committed Apr 13, 2015
1 parent ff6e775 commit 6c8398d
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 78 deletions.
2 changes: 1 addition & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -756,7 +756,6 @@ gulp.task('build/packages.dart', function(done) {
'build/transpile.dart', // Creates the folder structure needed by subsequent tasks.
['build/html.dart', 'build/copy.dart', 'build/multicopy.dart'],
'build/format.dart',
'build/pubspec.dart',
done
);
});
Expand All @@ -765,6 +764,7 @@ gulp.task('build/packages.dart', function(done) {
gulp.task('build.dart', function(done) {
runSequence(
'build/packages.dart',
'build/pubspec.dart',
'build/analyze.dart',
'build/pubbuild.dart',
done
Expand Down
1 change: 1 addition & 0 deletions karma-dart.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = function(config) {
// Unit test files needs to be included.
// Karma-dart generates `__adapter_unittest.dart` that imports these files.
{pattern: 'modules/*/test/**/*_spec.js', included: true},
{pattern: 'modules/*/test/**/*_spec.dart', included: true},
{pattern: 'tools/transpiler/spec/**/*_spec.js', included: true},

// These files are not included, they are imported by the unit tests above.
Expand Down
2 changes: 1 addition & 1 deletion modules/angular2/di.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

export {Inject, InjectPromise, InjectLazy, Injectable, Optional, DependencyAnnotation} from './src/di/annotations';
export {Injector} from './src/di/injector';
export {Binding, Dependency, bind} from './src/di/binding';
export {Binding, ResolvedBinding, Dependency, bind} from './src/di/binding';
export {Key, KeyRegistry} from './src/di/key';
export {KeyMetadataError, NoProviderError, ProviderError, AsyncBindingError, CyclicDependencyError,
InstantiationError, InvalidBindingError, NoAnnotationError} from './src/di/exceptions';
Expand Down
37 changes: 19 additions & 18 deletions modules/angular2/src/core/compiler/element_injector.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {isPresent, isBlank, Type, int, BaseException} from 'angular2/src/facade/lang';
import {Math} from 'angular2/src/facade/math';
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'angular2/di';
import {Injector, Key, Dependency, bind, Binding, ResolvedBinding, NoProviderError, ProviderError, CyclicDependencyError} from 'angular2/di';
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
import {EventEmitter, PropertySetter, Attribute, Query} from 'angular2/src/core/annotations/di';
import * as viewModule from 'angular2/src/core/compiler/view';
Expand Down Expand Up @@ -278,7 +278,7 @@ export class DirectiveDependency extends Dependency {
}
}

export class DirectiveBinding extends Binding {
export class DirectiveBinding extends ResolvedBinding {
callOnDestroy:boolean;
callOnChange:boolean;
callOnAllChangesDone:boolean;
Expand All @@ -292,13 +292,14 @@ export class DirectiveBinding extends Binding {
this.annotation = annotation;
}

static createFromBinding(b:Binding, annotation:Directive):Binding {
var deps = ListWrapper.map(b.dependencies, DirectiveDependency.createFrom);
return new DirectiveBinding(b.key, b.factory, deps, b.providedAsPromise, annotation);
static createFromBinding(b:Binding, annotation:Directive):DirectiveBinding {
var rb = b.resolve();
var deps = ListWrapper.map(rb.dependencies, DirectiveDependency.createFrom);
return new DirectiveBinding(rb.key, rb.factory, deps, rb.providedAsPromise, annotation);
}

static createFromType(type:Type, annotation:Directive):Binding {
var binding = bind(type).toClass(type);
static createFromType(type:Type, annotation:Directive):DirectiveBinding {
var binding = new Binding(type, {toClass: type});
return DirectiveBinding.createFromBinding(binding, annotation);
}

Expand Down Expand Up @@ -343,16 +344,16 @@ ElementInjector:
*/

export class ProtoElementInjector {
_binding0:Binding;
_binding1:Binding;
_binding2:Binding;
_binding3:Binding;
_binding4:Binding;
_binding5:Binding;
_binding6:Binding;
_binding7:Binding;
_binding8:Binding;
_binding9:Binding;
_binding0:ResolvedBinding;
_binding1:ResolvedBinding;
_binding2:ResolvedBinding;
_binding3:ResolvedBinding;
_binding4:ResolvedBinding;
_binding5:ResolvedBinding;
_binding6:ResolvedBinding;
_binding7:ResolvedBinding;
_binding8:ResolvedBinding;
_binding9:ResolvedBinding;
_binding0IsComponent:boolean;
_keyId0:int;
_keyId1:int;
Expand Down Expand Up @@ -642,7 +643,7 @@ export class ElementInjector extends TreeNode {
this._dynamicallyCreatedComponentBinding.key.id;
}

_new(binding:Binding) {
_new(binding:ResolvedBinding) {
if (this._constructionCounter++ > _MAX_DIRECTIVE_CONSTRUCTION_COUNTER) {
throw new CyclicDependencyError(binding.key);
}
Expand Down
154 changes: 115 additions & 39 deletions modules/angular2/src/di/binding.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import {Type, isBlank, isPresent} from 'angular2/src/facade/lang';
import {Type, isBlank, isPresent, CONST} from 'angular2/src/facade/lang';
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {reflector} from 'angular2/src/reflection/reflection';
import {Key} from './key';
import {Inject, InjectLazy, InjectPromise, Optional, DependencyAnnotation} from './annotations';
import {NoAnnotationError} from './exceptions';
import {NoAnnotationError, InvalidBindingError} from './exceptions';

export class Dependency {
key:Key;
asPromise:boolean;
lazy:boolean;
optional:boolean;
properties:List;

constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean, properties:List) {
this.key = key;
this.asPromise = asPromise;
Expand All @@ -24,80 +25,155 @@ export class Dependency {
}
}

var _EMPTY_LIST = []; // TODO: make const when supported

/**
* Declaration of a dependency binding.
*/
export class Binding {
token;
toClass:Type;
toValue;
toAlias;
toFactory:Function;
toAsyncFactory:Function;
dependencies:List;

@CONST()
constructor(
token,
{
toClass,
toValue,
toAlias,
toFactory,
toAsyncFactory,
deps
}) {
this.token = token;
this.toClass = toClass;
this.toValue = toValue;
this.toAlias = toAlias;
this.toFactory = toFactory;
this.toAsyncFactory = toAsyncFactory;
this.dependencies = deps;
}

resolve(): ResolvedBinding {
var factoryFn:Function;
var resolvedDeps;
var isAsync = false;
if (isPresent(this.toClass)) {
factoryFn = reflector.factory(this.toClass);
resolvedDeps = _dependenciesFor(this.toClass);
} else if (isPresent(this.toAlias)) {
factoryFn = (aliasInstance) => aliasInstance;
resolvedDeps = [Dependency.fromKey(Key.get(this.toAlias))];
} else if (isPresent(this.toFactory)) {
factoryFn = this.toFactory;
resolvedDeps = _constructDependencies(this.toFactory, this.dependencies);
} else if (isPresent(this.toAsyncFactory)) {
factoryFn = this.toAsyncFactory;
resolvedDeps = _constructDependencies(this.toAsyncFactory, this.dependencies);
isAsync = true;
} else {
factoryFn = () => this.toValue;
resolvedDeps = _EMPTY_LIST;
}

return new ResolvedBinding(
Key.get(this.token),
factoryFn,
resolvedDeps,
isAsync
);
}

static resolveAll(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 Type) {
resolved = bind(unresolved).toClass(unresolved).resolve();
} else if (unresolved instanceof Binding) {
resolved = unresolved.resolve();
} else if (unresolved instanceof List) {
resolved = Binding.resolveAll(unresolved);
} else if (unresolved instanceof BindingBuilder) {
throw new InvalidBindingError(unresolved.token);
} else {
throw new InvalidBindingError(unresolved);
}
resolvedList[i] = resolved;
}
return resolvedList;
}
}

/// Dependency binding with resolved keys and dependencies.
export class ResolvedBinding {
key:Key;
factory:Function;
dependencies:List;
dependencies:List<Dependency>;
providedAsPromise:boolean;

constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean) {
constructor(key:Key, factory:Function, dependencies:List<Dependency>, providedAsPromise:boolean) {
this.key = key;
this.factory = factory;
this.dependencies = dependencies;
this.providedAsPromise = providedAsPromise;
}
}

/**
* Provides fluent API for imperative construction of [Binding] objects.
*/
export function bind(token):BindingBuilder {
return new BindingBuilder(token);
}

/**
* Helper class for [bind] function.
*/
export class BindingBuilder {
token;

constructor(token) {
this.token = token;
}

toClass(type:Type):Binding {
return new Binding(
Key.get(this.token),
reflector.factory(type),
_dependenciesFor(type),
false
);
return new Binding(this.token, {toClass: type});
}

toValue(value):Binding {
return new Binding(
Key.get(this.token),
() => value,
[],
false
);
return new Binding(this.token, {toValue: value});
}

toAlias(aliasToken):Binding {
return new Binding(
Key.get(this.token),
(aliasInstance) => aliasInstance,
[Dependency.fromKey(Key.get(aliasToken))],
false
);
return new Binding(this.token, {toAlias: aliasToken});
}

toFactory(factoryFunction:Function, dependencies:List = null):Binding {
return new Binding(
Key.get(this.token),
factoryFunction,
this._constructDependencies(factoryFunction, dependencies),
false
);
return new Binding(this.token, {
toFactory: factoryFunction,
deps: dependencies
});
}

toAsyncFactory(factoryFunction:Function, dependencies:List = null):Binding {
return new Binding(
Key.get(this.token),
factoryFunction,
this._constructDependencies(factoryFunction, dependencies),
true
);
return new Binding(this.token, {
toAsyncFactory: factoryFunction,
deps: dependencies
});
}
}

_constructDependencies(factoryFunction:Function, dependencies:List) {
return isBlank(dependencies) ?
_dependenciesFor(factoryFunction) :
ListWrapper.map(dependencies, (t) => Dependency.fromKey(Key.get(t)));
}
function _constructDependencies(factoryFunction:Function, dependencies:List) {
return isBlank(dependencies) ?
_dependenciesFor(factoryFunction) :
ListWrapper.map(dependencies, (t) => Dependency.fromKey(Key.get(t)));
}

function _dependenciesFor(typeOrFunc):List {
Expand Down
27 changes: 8 additions & 19 deletions modules/angular2/src/di/injector.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Map, List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {Binding, BindingBuilder, bind} from './binding';
import {ProviderError, NoProviderError, InvalidBindingError,
import {ResolvedBinding, Binding, BindingBuilder, bind} from './binding';
import {ProviderError, NoProviderError,
AsyncBindingError, CyclicDependencyError, InstantiationError} from './exceptions';
import {FunctionWrapper, Type, isPresent, isBlank} from 'angular2/src/facade/lang';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
Expand Down Expand Up @@ -28,7 +28,7 @@ export class Injector {
_asyncStrategy: _AsyncInjectorStrategy;
_syncStrategy:_SyncInjectorStrategy;
constructor(bindings:List, {parent=null, defaultBindings=false}={}) {
var flatten = _flattenBindings(bindings, MapWrapper.create());
var flatten = _flattenBindings(Binding.resolveAll(bindings), MapWrapper.create());
this._bindings = this._createListOfBindings(flatten);
this._instances = this._createInstances();
this._parent = parent;
Expand Down Expand Up @@ -89,7 +89,7 @@ export class Injector {
}
}

_resolveDependencies(key:Key, binding:Binding, forceAsync:boolean):List {
_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);
Expand All @@ -115,7 +115,7 @@ export class Injector {
ListWrapper.get(this._bindings, key.id);

if (isBlank(binding) && this._defaultBindings) {
return bind(key.token).toClass(key.token);
return bind(key.token).toClass(key.token).resolve();
} else {
return binding;
}
Expand Down Expand Up @@ -166,7 +166,7 @@ class _SyncInjectorStrategy {
return this._createInstance(key, binding, deps);
}

_createInstance(key:Key, binding:Binding, deps:List) {
_createInstance(key:Key, binding:ResolvedBinding, deps:List) {
try {
var instance = FunctionWrapper.apply(binding.factory, deps);
this.injector._setInstance(key, instance);
Expand Down Expand Up @@ -227,7 +227,7 @@ class _AsyncInjectorStrategy {
return PromiseWrapper.reject(e);
}

_findOrCreate(key:Key, binding:Binding, deps:List) {
_findOrCreate(key:Key, binding:ResolvedBinding, deps:List) {
try {
var instance = this.injector._getInstance(key);
if (!_isWaiting(instance)) return instance;
Expand All @@ -246,21 +246,10 @@ class _AsyncInjectorStrategy {

function _flattenBindings(bindings:List, res:Map) {
ListWrapper.forEach(bindings, function (b) {
if (b instanceof Binding) {
if (b instanceof ResolvedBinding) {
MapWrapper.set(res, b.key.id, b);

} else if (b instanceof Type) {
var s = bind(b).toClass(b);
MapWrapper.set(res, s.key.id, s);

} else if (b instanceof List) {
_flattenBindings(b, res);

} else if (b instanceof BindingBuilder) {
throw new InvalidBindingError(b.token);

} else {
throw new InvalidBindingError(b);
}
});
return res;
Expand Down
Loading

0 comments on commit 6c8398d

Please sign in to comment.