feat(di): add support for optional dependencies
This commit is contained in:
parent
23786aaa92
commit
ba0a1ec459
|
@ -1,4 +1,4 @@
|
||||||
export {Inject, InjectPromise, InjectLazy, DependencyAnnotation} from './src/di/annotations';
|
export {Inject, InjectPromise, InjectLazy, Optional, DependencyAnnotation} from './src/di/annotations';
|
||||||
export {Injector} from './src/di/injector';
|
export {Injector} from './src/di/injector';
|
||||||
export {Binding, Dependency, bind} from './src/di/binding';
|
export {Binding, Dependency, bind} from './src/di/binding';
|
||||||
export {Key, KeyRegistry} from './src/di/key';
|
export {Key, KeyRegistry} from './src/di/key';
|
||||||
|
|
|
@ -91,14 +91,15 @@ export class DirectiveDependency extends Dependency {
|
||||||
depth:int;
|
depth:int;
|
||||||
eventEmitterName:string;
|
eventEmitterName:string;
|
||||||
|
|
||||||
constructor(key:Key, asPromise:boolean, lazy:boolean, properties:List, depth:int, eventEmitterName: string) {
|
constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean,
|
||||||
super(key, asPromise, lazy, properties);
|
properties:List, depth:int, eventEmitterName: string) {
|
||||||
|
super(key, asPromise, lazy, optional, properties);
|
||||||
this.depth = depth;
|
this.depth = depth;
|
||||||
this.eventEmitterName = eventEmitterName;
|
this.eventEmitterName = eventEmitterName;
|
||||||
}
|
}
|
||||||
|
|
||||||
static createFrom(d:Dependency):Dependency {
|
static createFrom(d:Dependency):Dependency {
|
||||||
return new DirectiveDependency(d.key, d.asPromise, d.lazy,
|
return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional,
|
||||||
d.properties, DirectiveDependency._depth(d.properties),
|
d.properties, DirectiveDependency._depth(d.properties),
|
||||||
DirectiveDependency._eventEmitterName(d.properties));
|
DirectiveDependency._eventEmitterName(d.properties));
|
||||||
}
|
}
|
||||||
|
@ -123,13 +124,11 @@ export class DirectiveDependency extends Dependency {
|
||||||
export class DirectiveBinding extends Binding {
|
export class DirectiveBinding extends Binding {
|
||||||
callOnDestroy:boolean;
|
callOnDestroy:boolean;
|
||||||
callOnChange:boolean;
|
callOnChange:boolean;
|
||||||
onCheck:boolean;
|
|
||||||
|
|
||||||
constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean, annotation:Directive) {
|
constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean, annotation:Directive) {
|
||||||
super(key, factory, dependencies, providedAsPromise);
|
super(key, factory, dependencies, providedAsPromise);
|
||||||
this.callOnDestroy = isPresent(annotation) && annotation.hasLifecycleHook(onDestroy);
|
this.callOnDestroy = isPresent(annotation) && annotation.hasLifecycleHook(onDestroy);
|
||||||
this.callOnChange = isPresent(annotation) && annotation.hasLifecycleHook(onChange);
|
this.callOnChange = isPresent(annotation) && annotation.hasLifecycleHook(onChange);
|
||||||
//this.onCheck = isPresent(annotation) && annotation.hasLifecycleHook(onCheck);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static createFromBinding(b:Binding, annotation:Directive):Binding {
|
static createFromBinding(b:Binding, annotation:Directive):Binding {
|
||||||
|
@ -405,7 +404,7 @@ export class ElementInjector extends TreeNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
get(token) {
|
get(token) {
|
||||||
return this._getByKey(Key.get(token), 0, null);
|
return this._getByKey(Key.get(token), 0, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasDirective(type:Type):boolean {
|
hasDirective(type:Type):boolean {
|
||||||
|
@ -489,7 +488,7 @@ export class ElementInjector extends TreeNode {
|
||||||
|
|
||||||
_getByDependency(dep:DirectiveDependency, requestor:Key) {
|
_getByDependency(dep:DirectiveDependency, requestor:Key) {
|
||||||
if (isPresent(dep.eventEmitterName)) return this._buildEventEmitter(dep);
|
if (isPresent(dep.eventEmitterName)) return this._buildEventEmitter(dep);
|
||||||
return this._getByKey(dep.key, dep.depth, requestor);
|
return this._getByKey(dep.key, dep.depth, dep.optional, requestor);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildEventEmitter(dep) {
|
_buildEventEmitter(dep) {
|
||||||
|
@ -515,7 +514,7 @@ export class ElementInjector extends TreeNode {
|
||||||
*
|
*
|
||||||
* Write benchmarks before doing this optimization.
|
* Write benchmarks before doing this optimization.
|
||||||
*/
|
*/
|
||||||
_getByKey(key:Key, depth:number, requestor:Key) {
|
_getByKey(key:Key, depth:number, optional:boolean, requestor:Key) {
|
||||||
var ei = this;
|
var ei = this;
|
||||||
|
|
||||||
if (! this._shouldIncludeSelf(depth)) {
|
if (! this._shouldIncludeSelf(depth)) {
|
||||||
|
@ -536,6 +535,8 @@ export class ElementInjector extends TreeNode {
|
||||||
|
|
||||||
if (isPresent(this._host) && this._host._isComponentKey(key)) {
|
if (isPresent(this._host) && this._host._isComponentKey(key)) {
|
||||||
return this._host.getComponent();
|
return this._host.getComponent();
|
||||||
|
} else if (optional) {
|
||||||
|
return this._appInjector(requestor).getOptional(key);
|
||||||
} else {
|
} else {
|
||||||
return this._appInjector(requestor).get(key);
|
return this._appInjector(requestor).get(key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,24 @@ export class InjectLazy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A parameter annotation that marks a dependency as optional.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* class AComponent {
|
||||||
|
* constructor(@Optional() dp:Dependency) {
|
||||||
|
* this.dp = dp;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export class Optional {
|
||||||
|
@CONST()
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `DependencyAnnotation` is used by the framework to extend DI.
|
* `DependencyAnnotation` is used by the framework to extend DI.
|
||||||
*
|
*
|
||||||
|
|
|
@ -2,20 +2,26 @@ import {FIELD, Type, isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||||
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||||
import {reflector} from 'angular2/src/reflection/reflection';
|
import {reflector} from 'angular2/src/reflection/reflection';
|
||||||
import {Key} from './key';
|
import {Key} from './key';
|
||||||
import {Inject, InjectLazy, InjectPromise, DependencyAnnotation} from './annotations';
|
import {Inject, InjectLazy, InjectPromise, Optional, DependencyAnnotation} from './annotations';
|
||||||
import {NoAnnotationError} from './exceptions';
|
import {NoAnnotationError} from './exceptions';
|
||||||
|
|
||||||
export class Dependency {
|
export class Dependency {
|
||||||
key:Key;
|
key:Key;
|
||||||
asPromise:boolean;
|
asPromise:boolean;
|
||||||
lazy:boolean;
|
lazy:boolean;
|
||||||
|
optional:boolean;
|
||||||
properties:List;
|
properties:List;
|
||||||
constructor(key:Key, asPromise:boolean, lazy:boolean, properties:List) {
|
constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean, properties:List) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.asPromise = asPromise;
|
this.asPromise = asPromise;
|
||||||
this.lazy = lazy;
|
this.lazy = lazy;
|
||||||
|
this.optional = optional;
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromKey(key:Key) {
|
||||||
|
return new Dependency(key, false, false, false, []);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Binding {
|
export class Binding {
|
||||||
|
@ -64,7 +70,7 @@ export class BindingBuilder {
|
||||||
return new Binding(
|
return new Binding(
|
||||||
Key.get(this.token),
|
Key.get(this.token),
|
||||||
(aliasInstance) => aliasInstance,
|
(aliasInstance) => aliasInstance,
|
||||||
[new Dependency(Key.get(aliasToken), false, false, [])],
|
[Dependency.fromKey(Key.get(aliasToken))],
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -90,7 +96,7 @@ export class BindingBuilder {
|
||||||
_constructDependencies(factoryFunction:Function, dependencies:List) {
|
_constructDependencies(factoryFunction:Function, dependencies:List) {
|
||||||
return isBlank(dependencies) ?
|
return isBlank(dependencies) ?
|
||||||
_dependenciesFor(factoryFunction) :
|
_dependenciesFor(factoryFunction) :
|
||||||
ListWrapper.map(dependencies, (t) => new Dependency(Key.get(t), false, false, []));
|
ListWrapper.map(dependencies, (t) => Dependency.fromKey(Key.get(t)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,36 +108,44 @@ function _dependenciesFor(typeOrFunc):List {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _extractToken(typeOrFunc, annotations) {
|
function _extractToken(typeOrFunc, annotations) {
|
||||||
var type;
|
|
||||||
var depProps = [];
|
var depProps = [];
|
||||||
|
var token = null;
|
||||||
|
var optional = false;
|
||||||
|
var lazy = false;
|
||||||
|
var asPromise = false;
|
||||||
|
|
||||||
for (var i = 0; i < annotations.length; ++i) {
|
for (var i = 0; i < annotations.length; ++i) {
|
||||||
var paramAnnotation = annotations[i];
|
var paramAnnotation = annotations[i];
|
||||||
|
|
||||||
if (paramAnnotation instanceof Type) {
|
if (paramAnnotation instanceof Type) {
|
||||||
type = paramAnnotation;
|
token = paramAnnotation;
|
||||||
|
|
||||||
} else if (paramAnnotation instanceof Inject) {
|
} else if (paramAnnotation instanceof Inject) {
|
||||||
return _createDependency(paramAnnotation.token, false, false, []);
|
token = paramAnnotation.token;
|
||||||
|
|
||||||
} else if (paramAnnotation instanceof InjectPromise) {
|
} else if (paramAnnotation instanceof InjectPromise) {
|
||||||
return _createDependency(paramAnnotation.token, true, false, []);
|
token = paramAnnotation.token;
|
||||||
|
asPromise = true;
|
||||||
|
|
||||||
} else if (paramAnnotation instanceof InjectLazy) {
|
} else if (paramAnnotation instanceof InjectLazy) {
|
||||||
return _createDependency(paramAnnotation.token, false, true, []);
|
token = paramAnnotation.token;
|
||||||
|
lazy = true;
|
||||||
|
|
||||||
|
} else if (paramAnnotation instanceof Optional) {
|
||||||
|
optional = true;
|
||||||
|
|
||||||
} else if (paramAnnotation instanceof DependencyAnnotation) {
|
} else if (paramAnnotation instanceof DependencyAnnotation) {
|
||||||
ListWrapper.push(depProps, paramAnnotation);
|
ListWrapper.push(depProps, paramAnnotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPresent(type)) {
|
if (isPresent(token)) {
|
||||||
return _createDependency(type, false, false, depProps);
|
return _createDependency(token, asPromise, lazy, optional, depProps);
|
||||||
} else {
|
} else {
|
||||||
throw new NoAnnotationError(typeOrFunc);
|
throw new NoAnnotationError(typeOrFunc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _createDependency(token, asPromise, lazy, depProps):Dependency {
|
function _createDependency(token, asPromise, lazy, optional, depProps):Dependency {
|
||||||
return new Dependency(Key.get(token), asPromise, lazy, depProps);
|
return new Dependency(Key.get(token), asPromise, lazy, optional, depProps);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,11 +38,15 @@ export class Injector {
|
||||||
}
|
}
|
||||||
|
|
||||||
get(token) {
|
get(token) {
|
||||||
return this._getByKey(Key.get(token), false, false);
|
return this._getByKey(Key.get(token), false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOptional(token) {
|
||||||
|
return this._getByKey(Key.get(token), false, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
asyncGet(token) {
|
asyncGet(token) {
|
||||||
return this._getByKey(Key.get(token), true, false);
|
return this._getByKey(Key.get(token), true, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
createChild(bindings:List):Injector {
|
createChild(bindings:List):Injector {
|
||||||
|
@ -60,9 +64,9 @@ export class Injector {
|
||||||
return ListWrapper.createFixedSize(Key.numberOfKeys + 1);
|
return ListWrapper.createFixedSize(Key.numberOfKeys + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
_getByKey(key:Key, returnPromise:boolean, returnLazy:boolean) {
|
_getByKey(key:Key, returnPromise:boolean, returnLazy:boolean, optional:boolean) {
|
||||||
if (returnLazy) {
|
if (returnLazy) {
|
||||||
return () => this._getByKey(key, returnPromise, false);
|
return () => this._getByKey(key, returnPromise, false, optional);
|
||||||
}
|
}
|
||||||
|
|
||||||
var strategy = returnPromise ? this._asyncStrategy : this._syncStrategy;
|
var strategy = returnPromise ? this._asyncStrategy : this._syncStrategy;
|
||||||
|
@ -74,14 +78,19 @@ export class Injector {
|
||||||
if (isPresent(instance)) return instance;
|
if (isPresent(instance)) return instance;
|
||||||
|
|
||||||
if (isPresent(this._parent)) {
|
if (isPresent(this._parent)) {
|
||||||
return this._parent._getByKey(key, returnPromise, returnLazy);
|
return this._parent._getByKey(key, returnPromise, returnLazy, optional);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optional) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
throw new NoProviderError(key);
|
||||||
}
|
}
|
||||||
throw new NoProviderError(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_resolveDependencies(key:Key, binding:Binding, forceAsync:boolean):List {
|
_resolveDependencies(key:Key, binding:Binding, forceAsync:boolean):List {
|
||||||
try {
|
try {
|
||||||
var getDependency = d => this._getByKey(d.key, forceAsync || d.asPromise, d.lazy);
|
var getDependency = d => this._getByKey(d.key, forceAsync || d.asPromise, d.lazy, d.optional);
|
||||||
return ListWrapper.map(binding.dependencies, getDependency);
|
return ListWrapper.map(binding.dependencies, getDependency);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._clear(key);
|
this._clear(key);
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {ProtoElementInjector, PreBuiltObjects, DirectiveBinding} from 'angular2/
|
||||||
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
|
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
|
||||||
import {EventEmitter} from 'angular2/src/core/annotations/events';
|
import {EventEmitter} from 'angular2/src/core/annotations/events';
|
||||||
import {onDestroy} from 'angular2/src/core/annotations/annotations';
|
import {onDestroy} from 'angular2/src/core/annotations/annotations';
|
||||||
import {Injector, Inject, bind} from 'angular2/di';
|
import {Optional, Injector, Inject, bind} from 'angular2/di';
|
||||||
import {View} from 'angular2/src/core/compiler/view';
|
import {View} from 'angular2/src/core/compiler/view';
|
||||||
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
|
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
|
||||||
import {NgElement} from 'angular2/src/core/dom/element';
|
import {NgElement} from 'angular2/src/core/dom/element';
|
||||||
|
@ -36,6 +36,13 @@ class NeedsDirective {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class OptionallyNeedsDirective {
|
||||||
|
dependency:SimpleDirective;
|
||||||
|
constructor(@Optional() dependency:SimpleDirective){
|
||||||
|
this.dependency = dependency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class NeedDirectiveFromParent {
|
class NeedDirectiveFromParent {
|
||||||
dependency:SimpleDirective;
|
dependency:SimpleDirective;
|
||||||
constructor(@Parent() dependency:SimpleDirective){
|
constructor(@Parent() dependency:SimpleDirective){
|
||||||
|
@ -342,6 +349,12 @@ export function main() {
|
||||||
toThrowError('No provider for SimpleDirective! (NeedDirectiveFromParent -> SimpleDirective)');
|
toThrowError('No provider for SimpleDirective! (NeedDirectiveFromParent -> SimpleDirective)');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should inject null when no directive found", function () {
|
||||||
|
var inj = injector([OptionallyNeedsDirective]);
|
||||||
|
var d = inj.get(OptionallyNeedsDirective);
|
||||||
|
expect(d.dependency).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
it("should accept SimpleDirective bindings instead of SimpleDirective types", function () {
|
it("should accept SimpleDirective bindings instead of SimpleDirective types", function () {
|
||||||
var inj = injector([
|
var inj = injector([
|
||||||
DirectiveBinding.createFromBinding(bind(SimpleDirective).toClass(SimpleDirective), null)
|
DirectiveBinding.createFromBinding(bind(SimpleDirective).toClass(SimpleDirective), null)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {describe, ddescribe, it, iit, expect, beforeEach} from 'angular2/test_lib';
|
import {describe, ddescribe, it, iit, expect, beforeEach} from 'angular2/test_lib';
|
||||||
import {Injector, Inject, InjectLazy, bind} from 'angular2/di';
|
import {Injector, Inject, InjectLazy, Optional, bind} from 'angular2/di';
|
||||||
|
|
||||||
class Engine {
|
class Engine {
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,13 @@ class CarWithLazyEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CarWithOptionalEngine {
|
||||||
|
engine;
|
||||||
|
constructor(@Optional() engine:Engine) {
|
||||||
|
this.engine = engine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class CarWithDashboard {
|
class CarWithDashboard {
|
||||||
engine:Engine;
|
engine:Engine;
|
||||||
dashboard:Dashboard;
|
dashboard:Dashboard;
|
||||||
|
@ -159,6 +166,15 @@ export function main() {
|
||||||
expect(car.engine).toBeAnInstanceOf(Engine);
|
expect(car.engine).toBeAnInstanceOf(Engine);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support optional dependencies', function () {
|
||||||
|
var injector = new Injector([
|
||||||
|
CarWithOptionalEngine
|
||||||
|
]);
|
||||||
|
|
||||||
|
var car = injector.get(CarWithOptionalEngine);
|
||||||
|
expect(car.engine).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
it("should flatten passed-in bindings", function () {
|
it("should flatten passed-in bindings", function () {
|
||||||
var injector = new Injector([
|
var injector = new Injector([
|
||||||
[[Engine, Car]]
|
[[Engine, Car]]
|
||||||
|
|
Loading…
Reference in New Issue