Skip to content

Commit ba0a1ec

Browse files
vsavkinmhevery
authored andcommitted
feat(di): add support for optional dependencies
1 parent 23786aa commit ba0a1ec

File tree

7 files changed

+102
-31
lines changed

7 files changed

+102
-31
lines changed

modules/angular2/di.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export {Inject, InjectPromise, InjectLazy, DependencyAnnotation} from './src/di/annotations';
1+
export {Inject, InjectPromise, InjectLazy, Optional, DependencyAnnotation} from './src/di/annotations';
22
export {Injector} from './src/di/injector';
33
export {Binding, Dependency, bind} from './src/di/binding';
44
export {Key, KeyRegistry} from './src/di/key';

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,15 @@ export class DirectiveDependency extends Dependency {
9191
depth:int;
9292
eventEmitterName:string;
9393

94-
constructor(key:Key, asPromise:boolean, lazy:boolean, properties:List, depth:int, eventEmitterName: string) {
95-
super(key, asPromise, lazy, properties);
94+
constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean,
95+
properties:List, depth:int, eventEmitterName: string) {
96+
super(key, asPromise, lazy, optional, properties);
9697
this.depth = depth;
9798
this.eventEmitterName = eventEmitterName;
9899
}
99100

100101
static createFrom(d:Dependency):Dependency {
101-
return new DirectiveDependency(d.key, d.asPromise, d.lazy,
102+
return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional,
102103
d.properties, DirectiveDependency._depth(d.properties),
103104
DirectiveDependency._eventEmitterName(d.properties));
104105
}
@@ -123,13 +124,11 @@ export class DirectiveDependency extends Dependency {
123124
export class DirectiveBinding extends Binding {
124125
callOnDestroy:boolean;
125126
callOnChange:boolean;
126-
onCheck:boolean;
127127

128128
constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean, annotation:Directive) {
129129
super(key, factory, dependencies, providedAsPromise);
130130
this.callOnDestroy = isPresent(annotation) && annotation.hasLifecycleHook(onDestroy);
131131
this.callOnChange = isPresent(annotation) && annotation.hasLifecycleHook(onChange);
132-
//this.onCheck = isPresent(annotation) && annotation.hasLifecycleHook(onCheck);
133132
}
134133

135134
static createFromBinding(b:Binding, annotation:Directive):Binding {
@@ -405,7 +404,7 @@ export class ElementInjector extends TreeNode {
405404
}
406405

407406
get(token) {
408-
return this._getByKey(Key.get(token), 0, null);
407+
return this._getByKey(Key.get(token), 0, false, null);
409408
}
410409

411410
hasDirective(type:Type):boolean {
@@ -489,7 +488,7 @@ export class ElementInjector extends TreeNode {
489488

490489
_getByDependency(dep:DirectiveDependency, requestor:Key) {
491490
if (isPresent(dep.eventEmitterName)) return this._buildEventEmitter(dep);
492-
return this._getByKey(dep.key, dep.depth, requestor);
491+
return this._getByKey(dep.key, dep.depth, dep.optional, requestor);
493492
}
494493

495494
_buildEventEmitter(dep) {
@@ -515,7 +514,7 @@ export class ElementInjector extends TreeNode {
515514
*
516515
* Write benchmarks before doing this optimization.
517516
*/
518-
_getByKey(key:Key, depth:number, requestor:Key) {
517+
_getByKey(key:Key, depth:number, optional:boolean, requestor:Key) {
519518
var ei = this;
520519

521520
if (! this._shouldIncludeSelf(depth)) {
@@ -536,6 +535,8 @@ export class ElementInjector extends TreeNode {
536535

537536
if (isPresent(this._host) && this._host._isComponentKey(key)) {
538537
return this._host.getComponent();
538+
} else if (optional) {
539+
return this._appInjector(requestor).getOptional(key);
539540
} else {
540541
return this._appInjector(requestor).get(key);
541542
}

modules/angular2/src/di/annotations.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,24 @@ export class InjectLazy {
5858
}
5959
}
6060

61+
/**
62+
* A parameter annotation that marks a dependency as optional.
63+
*
64+
* ```
65+
* class AComponent {
66+
* constructor(@Optional() dp:Dependency) {
67+
* this.dp = dp;
68+
* }
69+
* }
70+
* ```
71+
*
72+
*/
73+
export class Optional {
74+
@CONST()
75+
constructor() {
76+
}
77+
}
78+
6179
/**
6280
* `DependencyAnnotation` is used by the framework to extend DI.
6381
*

modules/angular2/src/di/binding.js

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,26 @@ import {FIELD, Type, isBlank, isPresent} from 'angular2/src/facade/lang';
22
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
33
import {reflector} from 'angular2/src/reflection/reflection';
44
import {Key} from './key';
5-
import {Inject, InjectLazy, InjectPromise, DependencyAnnotation} from './annotations';
5+
import {Inject, InjectLazy, InjectPromise, Optional, DependencyAnnotation} from './annotations';
66
import {NoAnnotationError} from './exceptions';
77

88
export class Dependency {
99
key:Key;
1010
asPromise:boolean;
1111
lazy:boolean;
12+
optional:boolean;
1213
properties:List;
13-
constructor(key:Key, asPromise:boolean, lazy:boolean, properties:List) {
14+
constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean, properties:List) {
1415
this.key = key;
1516
this.asPromise = asPromise;
1617
this.lazy = lazy;
18+
this.optional = optional;
1719
this.properties = properties;
1820
}
21+
22+
static fromKey(key:Key) {
23+
return new Dependency(key, false, false, false, []);
24+
}
1925
}
2026

2127
export class Binding {
@@ -64,7 +70,7 @@ export class BindingBuilder {
6470
return new Binding(
6571
Key.get(this.token),
6672
(aliasInstance) => aliasInstance,
67-
[new Dependency(Key.get(aliasToken), false, false, [])],
73+
[Dependency.fromKey(Key.get(aliasToken))],
6874
false
6975
);
7076
}
@@ -90,7 +96,7 @@ export class BindingBuilder {
9096
_constructDependencies(factoryFunction:Function, dependencies:List) {
9197
return isBlank(dependencies) ?
9298
_dependenciesFor(factoryFunction) :
93-
ListWrapper.map(dependencies, (t) => new Dependency(Key.get(t), false, false, []));
99+
ListWrapper.map(dependencies, (t) => Dependency.fromKey(Key.get(t)));
94100
}
95101
}
96102

@@ -102,36 +108,44 @@ function _dependenciesFor(typeOrFunc):List {
102108
}
103109

104110
function _extractToken(typeOrFunc, annotations) {
105-
var type;
106111
var depProps = [];
112+
var token = null;
113+
var optional = false;
114+
var lazy = false;
115+
var asPromise = false;
107116

108117
for (var i = 0; i < annotations.length; ++i) {
109118
var paramAnnotation = annotations[i];
110119

111120
if (paramAnnotation instanceof Type) {
112-
type = paramAnnotation;
121+
token = paramAnnotation;
113122

114123
} else if (paramAnnotation instanceof Inject) {
115-
return _createDependency(paramAnnotation.token, false, false, []);
124+
token = paramAnnotation.token;
116125

117126
} else if (paramAnnotation instanceof InjectPromise) {
118-
return _createDependency(paramAnnotation.token, true, false, []);
127+
token = paramAnnotation.token;
128+
asPromise = true;
119129

120130
} else if (paramAnnotation instanceof InjectLazy) {
121-
return _createDependency(paramAnnotation.token, false, true, []);
131+
token = paramAnnotation.token;
132+
lazy = true;
133+
134+
} else if (paramAnnotation instanceof Optional) {
135+
optional = true;
122136

123137
} else if (paramAnnotation instanceof DependencyAnnotation) {
124138
ListWrapper.push(depProps, paramAnnotation);
125139
}
126140
}
127141

128-
if (isPresent(type)) {
129-
return _createDependency(type, false, false, depProps);
142+
if (isPresent(token)) {
143+
return _createDependency(token, asPromise, lazy, optional, depProps);
130144
} else {
131145
throw new NoAnnotationError(typeOrFunc);
132146
}
133147
}
134148

135-
function _createDependency(token, asPromise, lazy, depProps):Dependency {
136-
return new Dependency(Key.get(token), asPromise, lazy, depProps);
149+
function _createDependency(token, asPromise, lazy, optional, depProps):Dependency {
150+
return new Dependency(Key.get(token), asPromise, lazy, optional, depProps);
137151
}

modules/angular2/src/di/injector.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,15 @@ export class Injector {
3838
}
3939

4040
get(token) {
41-
return this._getByKey(Key.get(token), false, false);
41+
return this._getByKey(Key.get(token), false, false, false);
42+
}
43+
44+
getOptional(token) {
45+
return this._getByKey(Key.get(token), false, false, true);
4246
}
4347

4448
asyncGet(token) {
45-
return this._getByKey(Key.get(token), true, false);
49+
return this._getByKey(Key.get(token), true, false, false);
4650
}
4751

4852
createChild(bindings:List):Injector {
@@ -60,9 +64,9 @@ export class Injector {
6064
return ListWrapper.createFixedSize(Key.numberOfKeys + 1);
6165
}
6266

63-
_getByKey(key:Key, returnPromise:boolean, returnLazy:boolean) {
67+
_getByKey(key:Key, returnPromise:boolean, returnLazy:boolean, optional:boolean) {
6468
if (returnLazy) {
65-
return () => this._getByKey(key, returnPromise, false);
69+
return () => this._getByKey(key, returnPromise, false, optional);
6670
}
6771

6872
var strategy = returnPromise ? this._asyncStrategy : this._syncStrategy;
@@ -74,14 +78,19 @@ export class Injector {
7478
if (isPresent(instance)) return instance;
7579

7680
if (isPresent(this._parent)) {
77-
return this._parent._getByKey(key, returnPromise, returnLazy);
81+
return this._parent._getByKey(key, returnPromise, returnLazy, optional);
82+
}
83+
84+
if (optional) {
85+
return null;
86+
} else {
87+
throw new NoProviderError(key);
7888
}
79-
throw new NoProviderError(key);
8089
}
8190

8291
_resolveDependencies(key:Key, binding:Binding, forceAsync:boolean):List {
8392
try {
84-
var getDependency = d => this._getByKey(d.key, forceAsync || d.asPromise, d.lazy);
93+
var getDependency = d => this._getByKey(d.key, forceAsync || d.asPromise, d.lazy, d.optional);
8594
return ListWrapper.map(binding.dependencies, getDependency);
8695
} catch (e) {
8796
this._clear(key);

modules/angular2/test/core/compiler/element_injector_spec.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {ProtoElementInjector, PreBuiltObjects, DirectiveBinding} from 'angular2/
55
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
66
import {EventEmitter} from 'angular2/src/core/annotations/events';
77
import {onDestroy} from 'angular2/src/core/annotations/annotations';
8-
import {Injector, Inject, bind} from 'angular2/di';
8+
import {Optional, Injector, Inject, bind} from 'angular2/di';
99
import {View} from 'angular2/src/core/compiler/view';
1010
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
1111
import {NgElement} from 'angular2/src/core/dom/element';
@@ -36,6 +36,13 @@ class NeedsDirective {
3636
}
3737
}
3838

39+
class OptionallyNeedsDirective {
40+
dependency:SimpleDirective;
41+
constructor(@Optional() dependency:SimpleDirective){
42+
this.dependency = dependency;
43+
}
44+
}
45+
3946
class NeedDirectiveFromParent {
4047
dependency:SimpleDirective;
4148
constructor(@Parent() dependency:SimpleDirective){
@@ -342,6 +349,12 @@ export function main() {
342349
toThrowError('No provider for SimpleDirective! (NeedDirectiveFromParent -> SimpleDirective)');
343350
});
344351

352+
it("should inject null when no directive found", function () {
353+
var inj = injector([OptionallyNeedsDirective]);
354+
var d = inj.get(OptionallyNeedsDirective);
355+
expect(d.dependency).toEqual(null);
356+
});
357+
345358
it("should accept SimpleDirective bindings instead of SimpleDirective types", function () {
346359
var inj = injector([
347360
DirectiveBinding.createFromBinding(bind(SimpleDirective).toClass(SimpleDirective), null)

modules/angular2/test/di/injector_spec.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {describe, ddescribe, it, iit, expect, beforeEach} from 'angular2/test_lib';
2-
import {Injector, Inject, InjectLazy, bind} from 'angular2/di';
2+
import {Injector, Inject, InjectLazy, Optional, bind} from 'angular2/di';
33

44
class Engine {
55
}
@@ -34,6 +34,13 @@ class CarWithLazyEngine {
3434
}
3535
}
3636

37+
class CarWithOptionalEngine {
38+
engine;
39+
constructor(@Optional() engine:Engine) {
40+
this.engine = engine;
41+
}
42+
}
43+
3744
class CarWithDashboard {
3845
engine:Engine;
3946
dashboard:Dashboard;
@@ -159,6 +166,15 @@ export function main() {
159166
expect(car.engine).toBeAnInstanceOf(Engine);
160167
});
161168

169+
it('should support optional dependencies', function () {
170+
var injector = new Injector([
171+
CarWithOptionalEngine
172+
]);
173+
174+
var car = injector.get(CarWithOptionalEngine);
175+
expect(car.engine).toEqual(null);
176+
});
177+
162178
it("should flatten passed-in bindings", function () {
163179
var injector = new Injector([
164180
[[Engine, Car]]

0 commit comments

Comments
 (0)