feat(di): add support for multi bindings

BREAKING CHANGE

Previously a content binding of a component was visible to the directives in its view with the host constraint. This is not the case any more. To access that binding, remove the constraint.
This commit is contained in:
vsavkin
2015-09-02 10:21:28 -07:00
committed by Victor Savkin
parent 2fea0c2602
commit 7736964a37
9 changed files with 387 additions and 174 deletions

View File

@ -676,7 +676,7 @@ export function main() {
`No provider for service! (${stringify(NeedsService) } -> service)`));
});
it("should instantiate directives that depend on bindings bindings of other directives", () => {
it("should instantiate directives that depend on bindings of other directives", () => {
var shadowInj = hostShadowInjectors(
ListWrapper.concat([DirectiveBinding.createFromType(SimpleDirective, new ComponentMetadata({
bindings: [bind('service').toValue('hostService')]})
@ -723,8 +723,30 @@ export function main() {
expect(inj.get(NeedsService).service).toEqual('viewService');
});
it("should not instantiate a directive in a view that has an ancestor dependency on bindings"+
" bindings of a decorator directive", () => {
it("should prioritize directive bindings over component bindings", () => {
var component = DirectiveBinding.createFromType(NeedsService, new ComponentMetadata({
bindings: [bind('service').toValue('compService')]}));
var directive = DirectiveBinding.createFromType(SomeOtherDirective, new DirectiveMetadata({
bindings: [bind('service').toValue('dirService')]}));
var inj = injector(ListWrapper.concat([component, directive], extraBindings), null, true);
expect(inj.get(NeedsService).service).toEqual('dirService');
});
it("should not instantiate a directive in a view that has a host dependency on bindings"+
" of the component", () => {
expect(() => {
hostShadowInjectors(
ListWrapper.concat([
DirectiveBinding.createFromType(SomeOtherDirective, new DirectiveMetadata({
bindings: [bind('service').toValue('hostService')]})
)], extraBindings),
ListWrapper.concat([NeedsServiceFromHost], extraBindings)
);
}).toThrowError(new RegExp("No provider for service!"));
});
it("should not instantiate a directive in a view that has a host dependency on bindings"+
" of a decorator directive", () => {
expect(() => {
hostShadowInjectors(
ListWrapper.concat([

View File

@ -30,7 +30,7 @@ import {
createVariableLocations
} from 'angular2/src/core/compiler/proto_view_factory';
import {Component, Directive} from 'angular2/metadata';
import {Key} from 'angular2/di';
import {Key, Binding} from 'angular2/di';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
import {
@ -222,7 +222,7 @@ export function main() {
}
function directiveBinding({metadata}: {metadata?: any} = {}) {
return new DirectiveBinding(Key.get("dummy"), null, [], [], [], metadata);
return new DirectiveBinding(Key.get("dummy"), null, null, metadata, [], []);
}
function createRenderProtoView(elementBinders = null, type: ViewType = null,

View File

@ -17,7 +17,8 @@ import {
Optional,
Inject,
BindingWithVisibility,
Visibility
Visibility,
Binding
} from 'angular2/di';
import {InjectorInlineStrategy, InjectorDynamicStrategy} from 'angular2/src/core/di/injector';
@ -104,7 +105,6 @@ export function main() {
bindings: dynamicBindings,
strategyClass: InjectorDynamicStrategy
}].forEach((context) => {
function createInjector(bindings: any[], dependencyProvider = null) {
return Injector.resolveAndCreate(bindings.concat(context['bindings']), dependencyProvider);
}
@ -186,6 +186,28 @@ export function main() {
expect(car).toBe(sportsCar);
});
it('should support multibindings', () => {
var injector = createInjector([
Engine,
new Binding(Car, {toClass: SportsCar, multi: true}),
new Binding(Car, {toClass: CarWithOptionalEngine, multi: true})
]);
var cars = injector.get(Car);
expect(cars.length).toEqual(2);
expect(cars[0]).toBeAnInstanceOf(SportsCar);
expect(cars[1]).toBeAnInstanceOf(CarWithOptionalEngine);
});
it('should support multibindings that are created using toAlias', () => {
var injector = createInjector(
[Engine, SportsCar, new Binding(Car, {toAlias: SportsCar, multi: true})]);
var cars = injector.get(Car);
expect(cars.length).toEqual(1);
expect(cars[0]).toBe(injector.get(SportsCar));
});
it('should throw when the aliased binding does not exist', () => {
var injector = createInjector([bind('car').toAlias(SportsCar)]);
var e = `No provider for ${stringify(SportsCar)}! (car -> ${stringify(SportsCar)})`;
@ -345,7 +367,8 @@ export function main() {
expect(injector.get(Car).engine).toEqual(e);
expect(depProvider.spy("getDependency"))
.toHaveBeenCalledWith(injector, bindings[0], bindings[0].dependencies[0]);
.toHaveBeenCalledWith(injector, bindings[0],
bindings[0].resolvedFactories[0].dependencies[0]);
});
});
@ -544,7 +567,6 @@ export function main() {
});
});
describe('resolve', () => {
it('should resolve and flatten', () => {
var bindings = Injector.resolve([Engine, [BrokenEngine]]);
@ -554,6 +576,36 @@ export function main() {
});
});
it("should support multi bindings", () => {
var binding = Injector.resolve([
new Binding(Engine, {toClass: BrokenEngine, multi: true}),
new Binding(Engine, {toClass: TurboEngine, multi: true})
])[0];
expect(binding.key.token).toBe(Engine);
expect(binding.multiBinding).toEqual(true);
expect(binding.resolvedFactories.length).toEqual(2);
});
it("should support multi bindings with only one binding", () => {
var binding =
Injector.resolve([new Binding(Engine, {toClass: BrokenEngine, multi: true})])[0];
expect(binding.key.token).toBe(Engine);
expect(binding.multiBinding).toEqual(true);
expect(binding.resolvedFactories.length).toEqual(1);
});
it("should throw when mixing multi bindings with regular bindings", () => {
expect(() => {
Injector.resolve([new Binding(Engine, {toClass: BrokenEngine, multi: true}), Engine]);
}).toThrowErrorWith("Cannot mix multi bindings and regular bindings");
expect(() => {
Injector.resolve([Engine, new Binding(Engine, {toClass: BrokenEngine, multi: true})]);
}).toThrowErrorWith("Cannot mix multi bindings and regular bindings");
});
it('should resolve forward references', () => {
var bindings = Injector.resolve([
forwardRef(() => Engine),
@ -565,9 +617,9 @@ export function main() {
var brokenEngineBinding = bindings[1];
var stringBinding = bindings[2];
expect(engineBinding.factory() instanceof Engine).toBe(true);
expect(brokenEngineBinding.factory() instanceof Engine).toBe(true);
expect(stringBinding.dependencies[0].key).toEqual(Key.get(Engine));
expect(engineBinding.resolvedFactories[0].factory() instanceof Engine).toBe(true);
expect(brokenEngineBinding.resolvedFactories[0].factory() instanceof Engine).toBe(true);
expect(stringBinding.resolvedFactories[0].dependencies[0].key).toEqual(Key.get(Engine));
});
it('should support overriding factory dependencies with dependency annotations', () => {
@ -576,10 +628,12 @@ export function main() {
.toFactory((e) => "result",
[[new InjectMetadata("dep"), new CustomDependencyMetadata()]])
]);
var binding = bindings[0];
expect(binding.dependencies[0].key.token).toEqual("dep");
expect(binding.dependencies[0].properties).toEqual([new CustomDependencyMetadata()]);
expect(binding.resolvedFactories[0].dependencies[0].key.token).toEqual("dep");
expect(binding.resolvedFactories[0].dependencies[0].properties)
.toEqual([new CustomDependencyMetadata()]);
});
});

View File

@ -20,8 +20,7 @@ export function main() {
it('should create a binding out of a type', () => {
var binding = PipeBinding.createFromType(MyPipe, new Pipe({name: 'my-pipe'}));
expect(binding.name).toEqual('my-pipe');
expect(binding.factory()).toBeAnInstanceOf(MyPipe);
expect(binding.dependencies.length).toEqual(0);
expect(binding.key.token).toEqual(MyPipe);
});
});
}