From c899b0a74c99ab29e50762aa68d8a0f3bde41c5c Mon Sep 17 00:00:00 2001 From: vsavkin Date: Thu, 18 Jun 2015 18:11:20 -0700 Subject: [PATCH] feat(element_injector): support multiple injectables with the same token --- .../src/core/compiler/element_injector.ts | 63 ++++++++++++------- modules/angular2/src/facade/collection.dart | 2 +- modules/angular2/src/facade/collection.ts | 2 +- .../angular2/src/forms/directives/shared.ts | 5 +- .../core/compiler/element_injector_spec.ts | 51 ++++++++++----- .../src/template_driven_forms/index.ts | 12 +++- 6 files changed, 92 insertions(+), 43 deletions(-) diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts index a961debbaa..968320cb6e 100644 --- a/modules/angular2/src/core/compiler/element_injector.ts +++ b/modules/angular2/src/core/compiler/element_injector.ts @@ -418,14 +418,8 @@ export class ProtoElementInjector { private static _createHostInjectorBindingData(dirBindings: List, bd: List, firstBindingIsComponent: boolean) { - var visitedIds: Map = new Map(); ListWrapper.forEach(dirBindings, dirBinding => { ListWrapper.forEach(dirBinding.resolvedHostInjectables, b => { - if (visitedIds.has(b.key.id)) { - throw new BaseException( - `Multiple directives defined the same host injectable: "${stringify(b.key.token)}"`); - } - visitedIds.set(b.key.id, true); bd.push(ProtoElementInjector._createBindingData(firstBindingIsComponent, dirBinding, dirBindings, ProtoElementInjector._createBinding(b))); @@ -948,6 +942,10 @@ export class ElementInjector extends TreeNode { } } + addDirectivesMatchingQuery(query: Query, list: any[]): void { + this._strategy.addDirectivesMatchingQuery(query, list); + } + private _buildQueries(): void { if (isPresent(this._proto)) { this._strategy.buildQueries(); @@ -1158,6 +1156,7 @@ interface _ElementInjectorStrategy { getComponent(): any; isComponentKey(key: Key): boolean; buildQueries(): void; + addDirectivesMatchingQuery(q: Query, res: any[]): void; getObjByKeyId(keyId: number, visibility: number): any; getDirectiveAtIndex(index: number): any; getComponentBinding(): DirectiveBinding; @@ -1234,17 +1233,18 @@ class ElementInjectorInlineStrategy implements _ElementInjectorStrategy { hydrate(): void { var p = this._protoStrategy; + var e = this._ei; - if (isPresent(p._keyId0)) this.getObjByKeyId(p._keyId0, LIGHT_DOM_AND_SHADOW_DOM); - if (isPresent(p._keyId1)) this.getObjByKeyId(p._keyId1, LIGHT_DOM_AND_SHADOW_DOM); - if (isPresent(p._keyId2)) this.getObjByKeyId(p._keyId2, LIGHT_DOM_AND_SHADOW_DOM); - if (isPresent(p._keyId3)) this.getObjByKeyId(p._keyId3, LIGHT_DOM_AND_SHADOW_DOM); - if (isPresent(p._keyId4)) this.getObjByKeyId(p._keyId4, LIGHT_DOM_AND_SHADOW_DOM); - if (isPresent(p._keyId5)) this.getObjByKeyId(p._keyId5, LIGHT_DOM_AND_SHADOW_DOM); - if (isPresent(p._keyId6)) this.getObjByKeyId(p._keyId6, LIGHT_DOM_AND_SHADOW_DOM); - if (isPresent(p._keyId7)) this.getObjByKeyId(p._keyId7, LIGHT_DOM_AND_SHADOW_DOM); - if (isPresent(p._keyId8)) this.getObjByKeyId(p._keyId8, LIGHT_DOM_AND_SHADOW_DOM); - if (isPresent(p._keyId9)) this.getObjByKeyId(p._keyId9, LIGHT_DOM_AND_SHADOW_DOM); + if (isPresent(p._keyId0) && isBlank(this._obj0)) this._obj0 = e._new(p._binding0); + if (isPresent(p._keyId1) && isBlank(this._obj1)) this._obj1 = e._new(p._binding1); + if (isPresent(p._keyId2) && isBlank(this._obj2)) this._obj2 = e._new(p._binding2); + if (isPresent(p._keyId3) && isBlank(this._obj3)) this._obj3 = e._new(p._binding3); + if (isPresent(p._keyId4) && isBlank(this._obj4)) this._obj4 = e._new(p._binding4); + if (isPresent(p._keyId5) && isBlank(this._obj5)) this._obj5 = e._new(p._binding5); + if (isPresent(p._keyId6) && isBlank(this._obj6)) this._obj6 = e._new(p._binding6); + if (isPresent(p._keyId7) && isBlank(this._obj7)) this._obj7 = e._new(p._binding7); + if (isPresent(p._keyId8) && isBlank(this._obj8)) this._obj8 = e._new(p._binding8); + if (isPresent(p._keyId9) && isBlank(this._obj9)) this._obj9 = e._new(p._binding9); } getComponent(): any { return this._obj0; } @@ -1288,6 +1288,20 @@ class ElementInjectorInlineStrategy implements _ElementInjectorStrategy { } } + addDirectivesMatchingQuery(query: Query, list: any[]): void { + var p = this._protoStrategy; + if (isPresent(p._binding0) && p._binding0.key.token === query.selector) list.push(this._obj0); + if (isPresent(p._binding1) && p._binding1.key.token === query.selector) list.push(this._obj1); + if (isPresent(p._binding2) && p._binding2.key.token === query.selector) list.push(this._obj2); + if (isPresent(p._binding3) && p._binding3.key.token === query.selector) list.push(this._obj3); + if (isPresent(p._binding4) && p._binding4.key.token === query.selector) list.push(this._obj4); + if (isPresent(p._binding5) && p._binding5.key.token === query.selector) list.push(this._obj5); + if (isPresent(p._binding6) && p._binding6.key.token === query.selector) list.push(this._obj6); + if (isPresent(p._binding7) && p._binding7.key.token === query.selector) list.push(this._obj7); + if (isPresent(p._binding8) && p._binding8.key.token === query.selector) list.push(this._obj8); + if (isPresent(p._binding9) && p._binding9.key.token === query.selector) list.push(this._obj9); + } + getObjByKeyId(keyId: number, visibility: number): any { var p = this._protoStrategy; @@ -1406,8 +1420,8 @@ class ElementInjectorDynamicStrategy implements _ElementInjectorStrategy { var p = this._protoStrategy; for (var i = 0; i < p._keyIds.length; i++) { - if (isPresent(p._keyIds[i])) { - this.getObjByKeyId(p._keyIds[i], LIGHT_DOM_AND_SHADOW_DOM); + if (isPresent(p._keyIds[i]) && isBlank(this._objs[i])) { + this._objs[i] = this._ei._new(p._bindings[i]); } } } @@ -1429,6 +1443,15 @@ class ElementInjectorDynamicStrategy implements _ElementInjectorStrategy { } } + addDirectivesMatchingQuery(query: Query, list: any[]): void { + var p = this._protoStrategy; + + for (var i = 0; i < p._bindings.length; i++) { + if (p._bindings[i].key.token === query.selector) list.push(this._objs[i]); + } + } + + getObjByKeyId(keyId: number, visibility: number): any { var p = this._protoStrategy; @@ -1518,8 +1541,6 @@ class QueryRef { } private _aggregateDirective(inj: ElementInjector, aggregator: List): void { - if (inj.hasDirective(this.query.selector)) { - aggregator.push(inj.get(this.query.selector)); - } + inj.addDirectivesMatchingQuery(this.query, aggregator); } } diff --git a/modules/angular2/src/facade/collection.dart b/modules/angular2/src/facade/collection.dart index a7a97b3e2f..fa1f93f87a 100644 --- a/modules/angular2/src/facade/collection.dart +++ b/modules/angular2/src/facade/collection.dart @@ -184,7 +184,7 @@ class ListWrapper { bool isListLikeIterable(obj) => obj is Iterable; -List iterableToList(Iterable ii) => ii.toList(); +List iterableToList(Iterable ii) => ii.toList(); void iterateListLike(iter, fn(item)) { assert(iter is Iterable); diff --git a/modules/angular2/src/facade/collection.ts b/modules/angular2/src/facade/collection.ts index 3941cf6ce8..3f6ba5de0a 100644 --- a/modules/angular2/src/facade/collection.ts +++ b/modules/angular2/src/facade/collection.ts @@ -253,7 +253,7 @@ export function iterateListLike(obj, fn: Function) { } } } -export function iterableToList(ii:Iterable):List { +export function iterableToList(ii: any): List { var res = []; for (var i of ii) { res.push(i); diff --git a/modules/angular2/src/forms/directives/shared.ts b/modules/angular2/src/forms/directives/shared.ts index c15c0cd3b7..a65d752117 100644 --- a/modules/angular2/src/forms/directives/shared.ts +++ b/modules/angular2/src/forms/directives/shared.ts @@ -38,7 +38,8 @@ export function setUpControl(c: Control, dir: NgControl) { export function composeNgValidator(ngValidators: QueryList): Function { if (isBlank(ngValidators)) return Validators.nullValidator; - return Validators.compose(iterableToList(ngValidators).map(v => v.validator)); + return Validators.compose( + (>iterableToList(ngValidators)).map(v => v.validator)); } function _throwError(dir: NgControl, message: string): void { @@ -49,5 +50,5 @@ function _throwError(dir: NgControl, message: string): void { export function setProperty(renderer: Renderer, elementRef: ElementRef, propName: string, propValue: any) { renderer.setElementProperty(elementRef.parentView.render, elementRef.boundElementIndex, propName, - propValue); + propValue); } \ No newline at end of file diff --git a/modules/angular2/test/core/compiler/element_injector_spec.ts b/modules/angular2/test/core/compiler/element_injector_spec.ts index 60d742c75d..ed2495e128 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.ts +++ b/modules/angular2/test/core/compiler/element_injector_spec.ts @@ -486,20 +486,6 @@ export function main() { expect(pei.getBindingAtIndex(i).key.token).toBe(i); } }); - - it('should throw whenever multiple directives declare the same host injectable', () => { - expect(() => { - createPei(null, 0, [ - DirectiveBinding.createFromType(SimpleDirective, new dirAnn.Component({ - hostInjector: [bind('injectable1').toValue('injectable1')] - })), - DirectiveBinding.createFromType(SomeOtherDirective, new dirAnn.Component({ - hostInjector: [bind('injectable1').toValue('injectable2')] - })) - ]); - }).toThrowError('Multiple directives defined the same host injectable: "injectable1"'); - }); - }); }); @@ -968,12 +954,47 @@ export function main() { }); it('should contain directives on the same injector', () => { - var inj = injector(ListWrapper.concat([NeedsQuery, CountingDirective], extraBindings), null, + var inj = injector(ListWrapper.concat([ + NeedsQuery, + CountingDirective + ], extraBindings), null, false, preBuildObjects); expectDirectives(inj.get(NeedsQuery).query, CountingDirective, [0]); }) + it('should contain multiple directives from the same injector', () => { + var inj = injector(ListWrapper.concat([ + NeedsQuery, + CountingDirective, + FancyCountingDirective, + bind(CountingDirective).toAlias(FancyCountingDirective) + ], extraBindings), null, + false, preBuildObjects); + + expect(inj.get(NeedsQuery).query.length).toEqual(2); + expect(inj.get(NeedsQuery).query.first).toBeAnInstanceOf(CountingDirective); + expect(inj.get(NeedsQuery).query.last).toBeAnInstanceOf(FancyCountingDirective); + }) + + it('should contain multiple directives from the same injector after linking', () => { + var inj = parentChildInjectors([], ListWrapper.concat([ + NeedsQuery, + CountingDirective, + FancyCountingDirective, + bind(CountingDirective).toAlias(FancyCountingDirective) + ], extraBindings)); + + var parent = inj.parent; + + inj.unlink(); + inj.link(parent); + + expect(inj.get(NeedsQuery).query.length).toEqual(2); + expect(inj.get(NeedsQuery).query.first).toBeAnInstanceOf(CountingDirective); + expect(inj.get(NeedsQuery).query.last).toBeAnInstanceOf(FancyCountingDirective); + }) + it('should contain the element when no directives are bound to the var binding', () => { var dirs = [NeedsQueryByVarBindings]; diff --git a/modules/examples/src/template_driven_forms/index.ts b/modules/examples/src/template_driven_forms/index.ts index ca79f0a923..fb6c571f4e 100644 --- a/modules/examples/src/template_driven_forms/index.ts +++ b/modules/examples/src/template_driven_forms/index.ts @@ -6,11 +6,14 @@ import { Component, Directive, View, - Ancestor + Ancestor, + NgValidator, + forwardRef, + Binding } from 'angular2/angular2'; import {formDirectives, NgControl, Validators, NgForm} from 'angular2/forms'; -import {RegExpWrapper, print, isPresent} from 'angular2/src/facade/lang'; +import {RegExpWrapper, print, isPresent, CONST_EXPR} from 'angular2/src/facade/lang'; import {reflector} from 'angular2/src/reflection/reflection'; import {ReflectionCapabilities} from 'angular2/src/reflection/reflection_capabilities'; @@ -33,7 +36,10 @@ class CheckoutModel { /** * Custom validator. */ -@Directive({selector: '[credit-card]'}) +const creditCardValidatorBinding = + CONST_EXPR(new Binding(NgValidator, {toAlias: forwardRef(() => CreditCardValidator)})); + +@Directive({selector: '[credit-card]', hostInjector: [creditCardValidatorBinding]}) class CreditCardValidator { get validator() { return CreditCardValidator.validate; }