diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts index a9bf3390b0..19b80f6c2b 100644 --- a/modules/angular2/src/core/compiler/element_injector.ts +++ b/modules/angular2/src/core/compiler/element_injector.ts @@ -30,6 +30,7 @@ import { InjectorDynamicStrategy, BindingWithVisibility } from 'angular2/src/core/di/injector'; +import {resolveBinding, ResolvedFactory} from 'angular2/src/core/di/binding'; import {AttributeMetadata, QueryMetadata} from '../metadata/di'; @@ -195,40 +196,32 @@ export class DirectiveDependency extends Dependency { } export class DirectiveBinding extends ResolvedBinding { - constructor(key: Key, factory: Function, dependencies: Dependency[], - public resolvedBindings: ResolvedBinding[], - public resolvedViewBindings: ResolvedBinding[], - public metadata: RenderDirectiveMetadata) { - super(key, factory, dependencies); + constructor(key: Key, factory: Function, deps: Dependency[], + public metadata: RenderDirectiveMetadata, + public bindings: Array, + public viewBindings: Array) { + super(key, [new ResolvedFactory(factory, deps)], false); } - get callOnDestroy(): boolean { return this.metadata.callOnDestroy; } - - get callOnChanges(): boolean { return this.metadata.callOnChanges; } - - get callAfterContentChecked(): boolean { return this.metadata.callAfterContentChecked; } - get displayName(): string { return this.key.displayName; } + get callOnDestroy(): boolean { return this.metadata.callOnDestroy; } + get eventEmitters(): string[] { return isPresent(this.metadata) && isPresent(this.metadata.events) ? this.metadata.events : []; } - get changeDetection() { return this.metadata.changeDetection; } - static createFromBinding(binding: Binding, meta: DirectiveMetadata): DirectiveBinding { if (isBlank(meta)) { meta = new DirectiveMetadata(); } - var rb = binding.resolve(); - var deps = ListWrapper.map(rb.dependencies, DirectiveDependency.createFrom); - var resolvedBindings = isPresent(meta.bindings) ? Injector.resolve(meta.bindings) : []; - var resolvedViewBindings = meta instanceof ComponentMetadata && isPresent(meta.viewBindings) ? - Injector.resolve(meta.viewBindings) : - []; + var rb = resolveBinding(binding); + var rf = rb.resolvedFactories[0]; + var deps = rf.dependencies.map(DirectiveDependency.createFrom); + var token = binding.token; var metadata = RenderDirectiveMetadata.create({ - id: stringify(rb.key.token), + id: stringify(binding.token), type: meta instanceof ComponentMetadata ? RenderDirectiveMetadata.COMPONENT_TYPE : RenderDirectiveMetadata.DIRECTIVE_TYPE, selector: meta.selector, @@ -236,29 +229,30 @@ export class DirectiveBinding extends ResolvedBinding { events: meta.events, host: isPresent(meta.host) ? MapWrapper.createFromStringMap(meta.host) : null, properties: meta.properties, - readAttributes: DirectiveBinding._readAttributes(deps), + readAttributes: DirectiveBinding._readAttributes(deps), - callOnDestroy: hasLifecycleHook(LifecycleEvent.OnDestroy, rb.key.token, meta), - callOnChanges: hasLifecycleHook(LifecycleEvent.OnChanges, rb.key.token, meta), - callDoCheck: hasLifecycleHook(LifecycleEvent.DoCheck, rb.key.token, meta), - callOnInit: hasLifecycleHook(LifecycleEvent.OnInit, rb.key.token, meta), - callAfterContentInit: hasLifecycleHook(LifecycleEvent.AfterContentInit, rb.key.token, meta), - callAfterContentChecked: - hasLifecycleHook(LifecycleEvent.AfterContentChecked, rb.key.token, meta), - callAfterViewInit: hasLifecycleHook(LifecycleEvent.AfterViewInit, rb.key.token, meta), - callAfterViewChecked: hasLifecycleHook(LifecycleEvent.AfterViewChecked, rb.key.token, meta), + callOnDestroy: hasLifecycleHook(LifecycleEvent.OnDestroy, token, meta), + callOnChanges: hasLifecycleHook(LifecycleEvent.OnChanges, token, meta), + callDoCheck: hasLifecycleHook(LifecycleEvent.DoCheck, token, meta), + callOnInit: hasLifecycleHook(LifecycleEvent.OnInit, token, meta), + callAfterContentInit: hasLifecycleHook(LifecycleEvent.AfterContentInit, token, meta), + callAfterContentChecked: hasLifecycleHook(LifecycleEvent.AfterContentChecked, token, meta), + callAfterViewInit: hasLifecycleHook(LifecycleEvent.AfterViewInit, token, meta), + callAfterViewChecked: hasLifecycleHook(LifecycleEvent.AfterViewChecked, token, meta), changeDetection: meta instanceof ComponentMetadata ? meta.changeDetection : null, exportAs: meta.exportAs }); - return new DirectiveBinding(rb.key, rb.factory, deps, resolvedBindings, resolvedViewBindings, - metadata); + var bindings = isPresent(meta.bindings) ? meta.bindings : []; + var viewBindigs = + meta instanceof ComponentMetadata && isPresent(meta.viewBindings) ? meta.viewBindings : []; + return new DirectiveBinding(rb.key, rf.factory, deps, metadata, bindings, viewBindigs); } - static _readAttributes(deps) { + static _readAttributes(deps: DirectiveDependency[]): string[] { var readAttributes = []; - ListWrapper.forEach(deps, (dep) => { + deps.forEach(dep => { if (isPresent(dep.attributeName)) { readAttributes.push(dep.attributeName); } @@ -316,7 +310,7 @@ export class ProtoElementInjector { eventEmitterAccessors: EventEmitterAccessor[][]; protoInjector: ProtoInjector; - static create(parent: ProtoElementInjector, index: number, bindings: ResolvedBinding[], + static create(parent: ProtoElementInjector, index: number, bindings: DirectiveBinding[], firstBindingIsComponent: boolean, distanceToParent: number, directiveVariableBindings: Map): ProtoElementInjector { var bd = []; @@ -326,43 +320,46 @@ export class ProtoElementInjector { if (firstBindingIsComponent) { ProtoElementInjector._createViewBindingsWithVisibility(bindings, bd); } - ProtoElementInjector._createBindingsWithVisibility(bindings, bd, firstBindingIsComponent); + + ProtoElementInjector._createBindingsWithVisibility(bindings, bd); return new ProtoElementInjector(parent, index, bd, distanceToParent, firstBindingIsComponent, directiveVariableBindings); } - private static _createDirectiveBindingWithVisibility(dirBindings: ResolvedBinding[], + private static _createDirectiveBindingWithVisibility(dirBindings: DirectiveBinding[], bd: BindingWithVisibility[], firstBindingIsComponent: boolean) { - ListWrapper.forEach(dirBindings, dirBinding => { + dirBindings.forEach(dirBinding => { bd.push(ProtoElementInjector._createBindingWithVisibility(firstBindingIsComponent, dirBinding, dirBindings, dirBinding)); }); } - private static _createBindingsWithVisibility(dirBindings: ResolvedBinding[], - bd: BindingWithVisibility[], - firstBindingIsComponent: boolean) { - ListWrapper.forEach(dirBindings, dirBinding => { - ListWrapper.forEach(dirBinding.resolvedBindings, b => { - bd.push(ProtoElementInjector._createBindingWithVisibility(firstBindingIsComponent, - dirBinding, dirBindings, b)); - }); + private static _createBindingsWithVisibility(dirBindings: DirectiveBinding[], + bd: BindingWithVisibility[]) { + var bindingsFromAllDirectives = []; + dirBindings.forEach(dirBinding => { + bindingsFromAllDirectives = + ListWrapper.concat(bindingsFromAllDirectives, dirBinding.bindings); }); + + var resolved = Injector.resolve(bindingsFromAllDirectives); + resolved.forEach(b => bd.push(new BindingWithVisibility(b, Visibility.Public))); } - private static _createBindingWithVisibility(firstBindingIsComponent, dirBinding, dirBindings, - binding) { + private static _createBindingWithVisibility(firstBindingIsComponent: boolean, + dirBinding: DirectiveBinding, + dirBindings: DirectiveBinding[], + binding: ResolvedBinding) { var isComponent = firstBindingIsComponent && dirBindings[0] === dirBinding; return new BindingWithVisibility(binding, isComponent ? Visibility.PublicAndPrivate : Visibility.Public); } - private static _createViewBindingsWithVisibility(bindings: ResolvedBinding[], + private static _createViewBindingsWithVisibility(dirBindings: DirectiveBinding[], bd: BindingWithVisibility[]) { - var db = bindings[0]; - ListWrapper.forEach(db.resolvedViewBindings, - b => bd.push(new BindingWithVisibility(b, Visibility.Private))); + var resolvedViewBindings = Injector.resolve(dirBindings[0].viewBindings); + resolvedViewBindings.forEach(b => bd.push(new BindingWithVisibility(b, Visibility.Private))); } @@ -852,7 +849,6 @@ interface _ElementInjectorStrategy { isComponentKey(key: Key): boolean; buildQueries(): void; addDirectivesMatchingQuery(q: QueryMetadata, res: any[]): void; - getComponentBinding(): DirectiveBinding; hydrate(): void; dehydrate(): void; } @@ -953,34 +949,44 @@ class ElementInjectorInlineStrategy implements _ElementInjectorStrategy { var p = this.injectorStrategy.protoStrategy; if (p.binding0 instanceof DirectiveBinding) { - this._ei._buildQueriesForDeps(p.binding0.dependencies); + this._ei._buildQueriesForDeps( + p.binding0.resolvedFactories[0].dependencies); } if (p.binding1 instanceof DirectiveBinding) { - this._ei._buildQueriesForDeps(p.binding1.dependencies); + this._ei._buildQueriesForDeps( + p.binding1.resolvedFactories[0].dependencies); } if (p.binding2 instanceof DirectiveBinding) { - this._ei._buildQueriesForDeps(p.binding2.dependencies); + this._ei._buildQueriesForDeps( + p.binding2.resolvedFactories[0].dependencies); } if (p.binding3 instanceof DirectiveBinding) { - this._ei._buildQueriesForDeps(p.binding3.dependencies); + this._ei._buildQueriesForDeps( + p.binding3.resolvedFactories[0].dependencies); } if (p.binding4 instanceof DirectiveBinding) { - this._ei._buildQueriesForDeps(p.binding4.dependencies); + this._ei._buildQueriesForDeps( + p.binding4.resolvedFactories[0].dependencies); } if (p.binding5 instanceof DirectiveBinding) { - this._ei._buildQueriesForDeps(p.binding5.dependencies); + this._ei._buildQueriesForDeps( + p.binding5.resolvedFactories[0].dependencies); } if (p.binding6 instanceof DirectiveBinding) { - this._ei._buildQueriesForDeps(p.binding6.dependencies); + this._ei._buildQueriesForDeps( + p.binding6.resolvedFactories[0].dependencies); } if (p.binding7 instanceof DirectiveBinding) { - this._ei._buildQueriesForDeps(p.binding7.dependencies); + this._ei._buildQueriesForDeps( + p.binding7.resolvedFactories[0].dependencies); } if (p.binding8 instanceof DirectiveBinding) { - this._ei._buildQueriesForDeps(p.binding8.dependencies); + this._ei._buildQueriesForDeps( + p.binding8.resolvedFactories[0].dependencies); } if (p.binding9 instanceof DirectiveBinding) { - this._ei._buildQueriesForDeps(p.binding9.dependencies); + this._ei._buildQueriesForDeps( + p.binding9.resolvedFactories[0].dependencies); } } @@ -1029,11 +1035,6 @@ class ElementInjectorInlineStrategy implements _ElementInjectorStrategy { list.push(i.obj9); } } - - getComponentBinding(): DirectiveBinding { - var p = this.injectorStrategy.protoStrategy; - return p.binding0; - } } /** @@ -1086,7 +1087,8 @@ class ElementInjectorDynamicStrategy implements _ElementInjectorStrategy { for (var i = 0; i < p.bindings.length; i++) { if (p.bindings[i] instanceof DirectiveBinding) { - this._ei._buildQueriesForDeps(p.bindings[i].dependencies); + this._ei._buildQueriesForDeps( + p.bindings[i].resolvedFactory.dependencies); } } } @@ -1104,11 +1106,6 @@ class ElementInjectorDynamicStrategy implements _ElementInjectorStrategy { } } } - - getComponentBinding(): DirectiveBinding { - var p = this.injectorStrategy.protoStrategy; - return p.bindings[0]; - } } export class QueryError extends BaseException { diff --git a/modules/angular2/src/core/di/binding.ts b/modules/angular2/src/core/di/binding.ts index 2f21baef43..e1fee975f1 100644 --- a/modules/angular2/src/core/di/binding.ts +++ b/modules/angular2/src/core/di/binding.ts @@ -6,7 +6,8 @@ import { CONST_EXPR, BaseException, stringify, - isArray + isArray, + normalizeBool } from 'angular2/src/core/facade/lang'; import {MapWrapper, ListWrapper} from 'angular2/src/core/facade/collection'; import {reflector} from 'angular2/src/core/reflection/reflection'; @@ -20,7 +21,11 @@ import { SkipSelfMetadata, DependencyMetadata } from './metadata'; -import {NoAnnotationError} from './exceptions'; +import { + NoAnnotationError, + MixingMultiBindingsWithRegularBindings, + InvalidBindingError +} from './exceptions'; import {resolveForwardRef} from './forward_ref'; /** @@ -174,46 +179,52 @@ export class Binding { * expect(injector.get(String)).toEqual('Value: 3'); * ``` */ - dependencies: any[]; + dependencies: Object[]; - constructor( - token, - {toClass, toValue, toAlias, toFactory, deps}: - {toClass?: Type, toValue?: any, toAlias?: any, toFactory?: Function, deps?: any[]}) { + _multi: boolean; + + constructor(token, {toClass, toValue, toAlias, toFactory, deps, multi}: { + toClass?: Type, + toValue?: any, + toAlias?: any, + toFactory?: Function, + deps?: Object[], + multi?: boolean + }) { this.token = token; this.toClass = toClass; this.toValue = toValue; this.toAlias = toAlias; this.toFactory = toFactory; this.dependencies = deps; + this._multi = multi; } /** - * Converts the {@link Binding} into {@link ResolvedBinding}. + * Used to create multiple bindings matching the same token. * - * {@link Injector} internally only uses {@link ResolvedBinding}, {@link Binding} contains - * convenience binding syntax. + * ## Example + * + * ```javascript + * var injector = Injector.resolveAndCreate([ + * new Binding("Strings", { toValue: "String1", multi: true}), + * new Binding("Strings", { toValue: "String2", multi: true}) + * ]); + * + * expect(injector.get("Strings")).toEqual(["String1", "String2"]); + * ``` + * + * Multi bindings and regular bindings cannot be mixed. The following + * will throw an exception: + * + * ```javascript + * var injector = Injector.resolveAndCreate([ + * new Binding("Strings", { toValue: "String1", multi: true}), + * new Binding("Strings", { toValue: "String2"}) + * ]); + * ``` */ - resolve(): ResolvedBinding { - var factoryFn: Function; - var resolvedDeps; - if (isPresent(this.toClass)) { - var toClass = resolveForwardRef(this.toClass); - factoryFn = reflector.factory(toClass); - resolvedDeps = _dependenciesFor(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 { - factoryFn = () => this.toValue; - resolvedDeps = _EMPTY_LIST; - } - - return new ResolvedBinding(Key.get(this.token), factoryFn, resolvedDeps); - } + get multi(): boolean { return normalizeBool(this._multi); } } /** @@ -230,6 +241,17 @@ export class ResolvedBinding { */ public key: Key, + /** + * Factory function which can return an instance of an object represented by a key. + */ + public resolvedFactories: ResolvedFactory[], + + public multiBinding: boolean) {} + get resolvedFactory(): ResolvedFactory { return this.resolvedFactories[0]; } +} + +export class ResolvedFactory { + constructor( /** * Factory function which can return an instance of an object represented by a key. */ @@ -370,6 +392,126 @@ export class BindingBuilder { } } +/** + * Resolve a single binding. + */ +export function resolveFactory(binding: Binding): ResolvedFactory { + var factoryFn: Function; + var resolvedDeps; + if (isPresent(binding.toClass)) { + var toClass = resolveForwardRef(binding.toClass); + factoryFn = reflector.factory(toClass); + resolvedDeps = _dependenciesFor(toClass); + } else if (isPresent(binding.toAlias)) { + factoryFn = (aliasInstance) => aliasInstance; + resolvedDeps = [Dependency.fromKey(Key.get(binding.toAlias))]; + } else if (isPresent(binding.toFactory)) { + factoryFn = binding.toFactory; + resolvedDeps = _constructDependencies(binding.toFactory, binding.dependencies); + } else { + factoryFn = () => binding.toValue; + resolvedDeps = _EMPTY_LIST; + } + return new ResolvedFactory(factoryFn, resolvedDeps); +} + +/** + * Converts the {@link Binding} into {@link ResolvedBinding}. + * + * {@link Injector} internally only uses {@link ResolvedBinding}, {@link Binding} contains + * convenience binding syntax. + */ +export function resolveBinding(binding: Binding): ResolvedBinding { + return new ResolvedBinding(Key.get(binding.token), [resolveFactory(binding)], false); +} + +/** + * Resolve a list of Bindings. + */ +export function resolveBindings(bindings: Array): ResolvedBinding[] { + var normalized = _createListOfBindings(_normalizeBindings(bindings, new Map())); + return normalized.map(b => { + if (b instanceof _NormalizedBinding) { + return new ResolvedBinding(b.key, [b.resolvedFactory], false); + + } else { + var arr = <_NormalizedBinding[]>b; + return new ResolvedBinding(arr[0].key, arr.map(_ => _.resolvedFactory), true); + } + }); +} + +/** + * The algorithm works as follows: + * + * [Binding] -> [_NormalizedBinding|[_NormalizedBinding]] -> [ResolvedBinding] + * + * _NormalizedBinding is essentially a resolved binding before it was grouped by key. + */ +class _NormalizedBinding { + constructor(public key: Key, public resolvedFactory: ResolvedFactory) {} +} + +function _createListOfBindings(flattenedBindings: Map): any[] { + return MapWrapper.values(flattenedBindings); +} + +function _normalizeBindings(bindings: Array, + res: Map): + Map { + ListWrapper.forEach(bindings, (b) => { + var key, factory, normalized; + + if (b instanceof Type) { + _normalizeBinding(bind(b).toClass(b), res); + + } else if (b instanceof Binding) { + _normalizeBinding(b, res); + + } else if (b instanceof Array) { + _normalizeBindings(b, res); + + } else if (b instanceof BindingBuilder) { + throw new InvalidBindingError(b.token); + + } else { + throw new InvalidBindingError(b); + } + }); + + return res; +} + +function _normalizeBinding(b: Binding, res: Map): + void { + var key = Key.get(b.token); + var factory = resolveFactory(b); + var normalized = new _NormalizedBinding(key, factory); + + if (b.multi) { + var existingBinding = res.get(key.id); + + if (existingBinding instanceof Array) { + existingBinding.push(normalized); + + } else if (isBlank(existingBinding)) { + res.set(key.id, [normalized]); + + } else { + throw new MixingMultiBindingsWithRegularBindings(existingBinding, b); + } + + } else { + var existingBinding = res.get(key.id); + + if (existingBinding instanceof Array) { + throw new MixingMultiBindingsWithRegularBindings(existingBinding, b); + } + + res.set(key.id, normalized); + } +} + function _constructDependencies(factoryFunction: Function, dependencies: any[]): Dependency[] { if (isBlank(dependencies)) { return _dependenciesFor(factoryFunction); diff --git a/modules/angular2/src/core/di/exceptions.ts b/modules/angular2/src/core/di/exceptions.ts index 1de301aff5..4d7772d032 100644 --- a/modules/angular2/src/core/di/exceptions.ts +++ b/modules/angular2/src/core/di/exceptions.ts @@ -167,3 +167,17 @@ export class OutOfBoundsError extends BaseException { toString(): string { return this.message; } } + +/** + * Thrown when a multi binding and a regular binding are bound to the same token. + */ +export class MixingMultiBindingsWithRegularBindings extends BaseException { + message: string; + constructor(binding1, binding2) { + super(); + this.message = "Cannot mix multi bindings and regular bindings, got: " + binding1.toString() + + " " + binding2.toString(); + } + + toString(): string { return this.message; } +} \ No newline at end of file diff --git a/modules/angular2/src/core/di/injector.ts b/modules/angular2/src/core/di/injector.ts index bcb19db768..48b6e30343 100644 --- a/modules/angular2/src/core/di/injector.ts +++ b/modules/angular2/src/core/di/injector.ts @@ -1,17 +1,28 @@ import {Map, MapWrapper, ListWrapper} from 'angular2/src/core/facade/collection'; -import {ResolvedBinding, Binding, Dependency, BindingBuilder, bind} from './binding'; +import { + ResolvedBinding, + Binding, + Dependency, + BindingBuilder, + ResolvedFactory, + bind, + resolveBindings +} from './binding'; import { AbstractBindingError, NoBindingError, CyclicDependencyError, InstantiationError, InvalidBindingError, - OutOfBoundsError + OutOfBoundsError, + MixingMultiBindingsWithRegularBindings } from './exceptions'; import {FunctionWrapper, Type, isPresent, isBlank, CONST_EXPR} from 'angular2/src/core/facade/lang'; import {Key} from './key'; import {resolveForwardRef} from './forward_ref'; import {SelfMetadata, HostMetadata, SkipSelfMetadata} from './metadata'; +import {reflector} from 'angular2/src/core/reflection/reflection'; + // Threshold for the dynamic version const _MAX_CONSTRUCTION_COUNTER = 10; @@ -428,9 +439,7 @@ export class Injector { * `fromResolvedBindings` and `createChildFromResolved`. */ static resolve(bindings: Array): ResolvedBinding[] { - var resolvedBindings = _resolveBindings(bindings); - var flatten = _flattenBindings(resolvedBindings, new Map()); - return _createListOfBindings(flatten); + return resolveBindings(bindings); } /** @@ -577,19 +586,32 @@ export class Injector { * @returns an object created using binding. */ instantiateResolved(binding: ResolvedBinding): any { - return this._instantiate(binding, Visibility.PublicAndPrivate); + return this._instantiateBinding(binding, Visibility.PublicAndPrivate); } _new(binding: ResolvedBinding, visibility: Visibility): any { if (this._constructionCounter++ > this._strategy.getMaxNumberOfObjects()) { throw new CyclicDependencyError(this, binding.key); } - return this._instantiate(binding, visibility); + return this._instantiateBinding(binding, visibility); } - private _instantiate(binding: ResolvedBinding, visibility: Visibility): any { - var factory = binding.factory; - var deps = binding.dependencies; + private _instantiateBinding(binding: ResolvedBinding, visibility: Visibility): any { + if (binding.multiBinding) { + var res = ListWrapper.createFixedSize(binding.resolvedFactories.length); + for (var i = 0; i < binding.resolvedFactories.length; ++i) { + res[i] = this._instantiate(binding, binding.resolvedFactories[i], visibility); + } + return res; + } else { + return this._instantiate(binding, binding.resolvedFactories[0], visibility); + } + } + + private _instantiate(binding: ResolvedBinding, resolvedFactory: ResolvedFactory, + visibility: Visibility): any { + var factory = resolvedFactory.factory; + var deps = resolvedFactory.dependencies; var length = deps.length; var d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17, d18, d19; @@ -801,45 +823,6 @@ export class Injector { var INJECTOR_KEY = Key.get(Injector); -function _resolveBindings(bindings: Array): ResolvedBinding[] { - var resolvedList = ListWrapper.createFixedSize(bindings.length); - for (var i = 0; i < bindings.length; i++) { - var unresolved = resolveForwardRef(bindings[i]); - var resolved; - if (unresolved instanceof ResolvedBinding) { - resolved = unresolved; // ha-ha! I'm easily amused - } else if (unresolved instanceof Type) { - resolved = bind(unresolved).toClass(unresolved).resolve(); - } else if (unresolved instanceof Binding) { - resolved = unresolved.resolve(); - } else if (unresolved instanceof Array) { - resolved = _resolveBindings(unresolved); - } else if (unresolved instanceof BindingBuilder) { - throw new InvalidBindingError(unresolved.token); - } else { - throw new InvalidBindingError(unresolved); - } - resolvedList[i] = resolved; - } - return resolvedList; -} - -function _createListOfBindings(flattenedBindings: Map): ResolvedBinding[] { - return MapWrapper.values(flattenedBindings); -} - -function _flattenBindings(bindings: Array, - res: Map): Map { - ListWrapper.forEach(bindings, function(b) { - if (b instanceof ResolvedBinding) { - res.set(b.key.id, b); - } else if (b instanceof Array) { - _flattenBindings(b, res); - } - }); - return res; -} - function _mapBindings(injector: Injector, fn: Function): any[] { var res = []; for (var i = 0; i < injector._proto.numberOfBindings; ++i) { diff --git a/modules/angular2/src/core/pipes/pipe_binding.ts b/modules/angular2/src/core/pipes/pipe_binding.ts index 93fcf7ef7e..13996a1f7b 100644 --- a/modules/angular2/src/core/pipes/pipe_binding.ts +++ b/modules/angular2/src/core/pipes/pipe_binding.ts @@ -1,15 +1,17 @@ import {Type} from 'angular2/src/core/facade/lang'; -import {Key, Dependency, ResolvedBinding, Binding} from 'angular2/di'; +import {Key, ResolvedBinding, Binding} from 'angular2/di'; +import {ResolvedFactory, resolveBinding} from 'angular2/src/core/di/binding'; import {PipeMetadata} from '../metadata/directives'; export class PipeBinding extends ResolvedBinding { - constructor(public name: string, key: Key, factory: Function, dependencies: Dependency[]) { - super(key, factory, dependencies); + constructor(public name: string, key: Key, resolvedFactories: ResolvedFactory[], + multiBinding: boolean) { + super(key, resolvedFactories, multiBinding); } static createFromType(type: Type, metadata: PipeMetadata): PipeBinding { var binding = new Binding(type, {toClass: type}); - var rb = binding.resolve(); - return new PipeBinding(metadata.name, rb.key, rb.factory, rb.dependencies); + var rb = resolveBinding(binding); + return new PipeBinding(metadata.name, rb.key, rb.resolvedFactories, rb.multiBinding); } } diff --git a/modules/angular2/test/core/compiler/element_injector_spec.ts b/modules/angular2/test/core/compiler/element_injector_spec.ts index 1371ac88dd..43ad44cf44 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.ts +++ b/modules/angular2/test/core/compiler/element_injector_spec.ts @@ -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([ diff --git a/modules/angular2/test/core/compiler/proto_view_factory_spec.ts b/modules/angular2/test/core/compiler/proto_view_factory_spec.ts index 149b183e46..843e70a655 100644 --- a/modules/angular2/test/core/compiler/proto_view_factory_spec.ts +++ b/modules/angular2/test/core/compiler/proto_view_factory_spec.ts @@ -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, diff --git a/modules/angular2/test/core/di/injector_spec.ts b/modules/angular2/test/core/di/injector_spec.ts index 768b2dc086..40e5871708 100644 --- a/modules/angular2/test/core/di/injector_spec.ts +++ b/modules/angular2/test/core/di/injector_spec.ts @@ -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()]); }); }); diff --git a/modules/angular2/test/core/pipes/pipe_binding_spec.ts b/modules/angular2/test/core/pipes/pipe_binding_spec.ts index 191a63f366..7cfc1ac092 100644 --- a/modules/angular2/test/core/pipes/pipe_binding_spec.ts +++ b/modules/angular2/test/core/pipes/pipe_binding_spec.ts @@ -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); }); }); }