From b1c9bf14b2baabbb50ae69f906f5186abb13b627 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Wed, 20 May 2015 17:26:22 +0200 Subject: [PATCH] feat(ElementInjector): support an arbitrary number of bindings fixes #1853 --- .../src/core/compiler/element_injector.ts | 811 +++++++----- .../core/compiler/element_injector_spec.js | 1094 +++++++++-------- 2 files changed, 1068 insertions(+), 837 deletions(-) diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts index 179f2890cc..4d41a9ef99 100644 --- a/modules/angular2/src/core/compiler/element_injector.ts +++ b/modules/angular2/src/core/compiler/element_injector.ts @@ -33,7 +33,7 @@ import {QueryList} from './query_list'; import {reflector} from 'angular2/src/reflection/reflection'; import {DirectiveMetadata} from 'angular2/src/render/api'; - +// Threshold for the dynamic version var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10; var _undefined = new Object(); @@ -64,13 +64,10 @@ class StaticKeys { export class TreeNode> { _parent: T; - _head: T; - _tail: T; - _next: T; + _head: T = null; + _tail: T = null; + _next: T = null; constructor(parent: T) { - this._head = null; - this._tail = null; - this._next = null; if (isPresent(parent)) parent.addChild(this); } @@ -250,13 +247,13 @@ export class DirectiveBinding extends ResolvedBinding { super(key, factory, dependencies, providedAsPromise); } - get callOnDestroy() { return this.metadata.callOnDestroy; } + get callOnDestroy(): boolean { return this.metadata.callOnDestroy; } - get callOnChange() { return this.metadata.callOnChange; } + get callOnChange(): boolean { return this.metadata.callOnChange; } - get callOnAllChangesDone() { return this.metadata.callOnAllChangesDone; } + get callOnAllChangesDone(): boolean { return this.metadata.callOnAllChangesDone; } - get displayName() { return this.key.displayName; } + get displayName(): string { return this.key.displayName; } get eventEmitters(): List { return isPresent(this.metadata) && isPresent(this.metadata.events) ? this.metadata.events : []; @@ -407,44 +404,7 @@ ElementInjector: http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/ */ export class ProtoElementInjector { - // only _binding0 can contain a component - _binding0: ResolvedBinding; - _binding1: ResolvedBinding; - _binding2: ResolvedBinding; - _binding3: ResolvedBinding; - _binding4: ResolvedBinding; - _binding5: ResolvedBinding; - _binding6: ResolvedBinding; - _binding7: ResolvedBinding; - _binding8: ResolvedBinding; - _binding9: ResolvedBinding; - - _keyId0: int; - _keyId1: int; - _keyId2: int; - _keyId3: int; - _keyId4: int; - _keyId5: int; - _keyId6: int; - _keyId7: int; - _keyId8: int; - _keyId9: int; - - _visibility0: number; - _visibility1: number; - _visibility2: number; - _visibility3: number; - _visibility4: number; - _visibility5: number; - _visibility6: number; - _visibility7: number; - _visibility8: number; - _visibility9: number; - - parent: ProtoElementInjector; - index: int; view: viewModule.AppView; - distanceToParent: number; attributes: Map; eventEmitterAccessors: List>; hostActionAccessors: List>; @@ -458,9 +418,9 @@ export class ProtoElementInjector { /** The variable name that will be set to $implicit for the element. */ exportImplicitName: string; - _firstBindingIsComponent: boolean; + _strategy; - static create(parent: ProtoElementInjector, index: int, bindings: List, + static create(parent: ProtoElementInjector, index: number, bindings: List, firstBindingIsComponent: boolean, distanceToParent: number) { var bd = []; @@ -508,123 +468,18 @@ export class ProtoElementInjector { return new ResolvedBinding(b.key, b.factory, deps, b.providedAsPromise); } - constructor(parent: ProtoElementInjector, index: int, bd: List, - distanceToParent: number, firstBindingIsComponent: boolean) { - this.parent = parent; - this.index = index; - this.distanceToParent = distanceToParent; + constructor(public parent: ProtoElementInjector, public index: int, bd: List, + public distanceToParent: number, public _firstBindingIsComponent: boolean) { this.exportComponent = false; this.exportElement = false; - this._firstBindingIsComponent = firstBindingIsComponent; - - this._binding0 = null; - this._keyId0 = null; - this._visibility0 = null; - this._binding1 = null; - this._keyId1 = null; - this._visibility1 = null; - this._binding2 = null; - this._keyId2 = null; - this._visibility2 = null; - this._binding3 = null; - this._keyId3 = null; - this._visibility3 = null; - this._binding4 = null; - this._keyId4 = null; - this._visibility4 = null; - this._binding5 = null; - this._keyId5 = null; - this._visibility5 = null; - this._binding6 = null; - this._keyId6 = null; - this._visibility6 = null; - this._binding7 = null; - this._keyId7 = null; - this._visibility7 = null; - this._binding8 = null; - this._keyId8 = null; - this._visibility8 = null; - this._binding9 = null; - this._keyId9 = null; - this._visibility9 = null; var length = bd.length; this.eventEmitterAccessors = ListWrapper.createFixedSize(length); this.hostActionAccessors = ListWrapper.createFixedSize(length); - if (length > 0) { - this._binding0 = bd[0].binding; - this._keyId0 = bd[0].getKeyId(); - this._visibility0 = bd[0].visibility; - this.eventEmitterAccessors[0] = bd[0].createEventEmitterAccessors(); - this.hostActionAccessors[0] = bd[0].createHostActionAccessors(); - } - if (length > 1) { - this._binding1 = bd[1].binding; - this._keyId1 = bd[1].getKeyId(); - this._visibility1 = bd[1].visibility; - this.eventEmitterAccessors[1] = bd[1].createEventEmitterAccessors(); - this.hostActionAccessors[1] = bd[1].createHostActionAccessors(); - } - if (length > 2) { - this._binding2 = bd[2].binding; - this._keyId2 = bd[2].getKeyId(); - this._visibility2 = bd[2].visibility; - this.eventEmitterAccessors[2] = bd[2].createEventEmitterAccessors(); - this.hostActionAccessors[2] = bd[2].createHostActionAccessors(); - } - if (length > 3) { - this._binding3 = bd[3].binding; - this._keyId3 = bd[3].getKeyId(); - this._visibility3 = bd[3].visibility; - this.eventEmitterAccessors[3] = bd[3].createEventEmitterAccessors(); - this.hostActionAccessors[3] = bd[3].createHostActionAccessors(); - } - if (length > 4) { - this._binding4 = bd[4].binding; - this._keyId4 = bd[4].getKeyId(); - this._visibility4 = bd[4].visibility; - this.eventEmitterAccessors[4] = bd[4].createEventEmitterAccessors(); - this.hostActionAccessors[4] = bd[4].createHostActionAccessors(); - } - if (length > 5) { - this._binding5 = bd[5].binding; - this._keyId5 = bd[5].getKeyId(); - this._visibility5 = bd[5].visibility; - this.eventEmitterAccessors[5] = bd[5].createEventEmitterAccessors(); - this.hostActionAccessors[5] = bd[5].createHostActionAccessors(); - } - if (length > 6) { - this._binding6 = bd[6].binding; - this._keyId6 = bd[6].getKeyId(); - this._visibility6 = bd[6].visibility; - this.eventEmitterAccessors[6] = bd[6].createEventEmitterAccessors(); - this.hostActionAccessors[6] = bd[6].createHostActionAccessors(); - } - if (length > 7) { - this._binding7 = bd[7].binding; - this._keyId7 = bd[7].getKeyId(); - this._visibility7 = bd[7].visibility; - this.eventEmitterAccessors[7] = bd[7].createEventEmitterAccessors(); - this.hostActionAccessors[7] = bd[7].createHostActionAccessors(); - } - if (length > 8) { - this._binding8 = bd[8].binding; - this._keyId8 = bd[8].getKeyId(); - this._visibility8 = bd[8].visibility; - this.eventEmitterAccessors[8] = bd[8].createEventEmitterAccessors(); - this.hostActionAccessors[8] = bd[8].createHostActionAccessors(); - } - if (length > 9) { - this._binding9 = bd[9].binding; - this._keyId9 = bd[9].getKeyId(); - this._visibility9 = bd[9].visibility; - this.eventEmitterAccessors[9] = bd[9].createEventEmitterAccessors(); - this.hostActionAccessors[9] = bd[9].createHostActionAccessors(); - } - if (length > 10) { - throw 'Maximum number of directives per element has been reached.'; - } + this._strategy = length > _MAX_DIRECTIVE_CONSTRUCTION_COUNTER ? + new ProtoElementInjectorDynamicStrategy(this, bd) : + new ProtoElementInjectorInlineStrategy(this, bd); } instantiate(parent: ElementInjector): ElementInjector { @@ -633,9 +488,129 @@ export class ProtoElementInjector { directParent(): ProtoElementInjector { return this.distanceToParent < 2 ? this.parent : null; } - get hasBindings(): boolean { return isPresent(this._binding0); } + get hasBindings(): boolean { return this._strategy.hasBindings(); } - getBindingAtIndex(index: int) { + getBindingAtIndex(index: number): any { return this._strategy.getBindingAtIndex(index); } +} + +/** + * Strategy used by the `ProtoElementInjector` when the number of bindings is 10 or less. + * In such a case, inlining fields is benefitial for performances. + */ +// TODO(vicb): add an interface +class ProtoElementInjectorInlineStrategy { + // only _binding0 can contain a component + _binding0: ResolvedBinding = null; + _binding1: ResolvedBinding = null; + _binding2: ResolvedBinding = null; + _binding3: ResolvedBinding = null; + _binding4: ResolvedBinding = null; + _binding5: ResolvedBinding = null; + _binding6: ResolvedBinding = null; + _binding7: ResolvedBinding = null; + _binding8: ResolvedBinding = null; + _binding9: ResolvedBinding = null; + + _keyId0: number = null; + _keyId1: number = null; + _keyId2: number = null; + _keyId3: number = null; + _keyId4: number = null; + _keyId5: number = null; + _keyId6: number = null; + _keyId7: number = null; + _keyId8: number = null; + _keyId9: number = null; + + _visibility0: number = null; + _visibility1: number = null; + _visibility2: number = null; + _visibility3: number = null; + _visibility4: number = null; + _visibility5: number = null; + _visibility6: number = null; + _visibility7: number = null; + _visibility8: number = null; + _visibility9: number = null; + + constructor(protoEI: ProtoElementInjector, bd: List) { + var length = bd.length; + + if (length > 0) { + this._binding0 = bd[0].binding; + this._keyId0 = bd[0].getKeyId(); + this._visibility0 = bd[0].visibility; + protoEI.eventEmitterAccessors[0] = bd[0].createEventEmitterAccessors(); + protoEI.hostActionAccessors[0] = bd[0].createHostActionAccessors(); + } + if (length > 1) { + this._binding1 = bd[1].binding; + this._keyId1 = bd[1].getKeyId(); + this._visibility1 = bd[1].visibility; + protoEI.eventEmitterAccessors[1] = bd[1].createEventEmitterAccessors(); + protoEI.hostActionAccessors[1] = bd[1].createHostActionAccessors(); + } + if (length > 2) { + this._binding2 = bd[2].binding; + this._keyId2 = bd[2].getKeyId(); + this._visibility2 = bd[2].visibility; + protoEI.eventEmitterAccessors[2] = bd[2].createEventEmitterAccessors(); + protoEI.hostActionAccessors[2] = bd[2].createHostActionAccessors(); + } + if (length > 3) { + this._binding3 = bd[3].binding; + this._keyId3 = bd[3].getKeyId(); + this._visibility3 = bd[3].visibility; + protoEI.eventEmitterAccessors[3] = bd[3].createEventEmitterAccessors(); + protoEI.hostActionAccessors[3] = bd[3].createHostActionAccessors(); + } + if (length > 4) { + this._binding4 = bd[4].binding; + this._keyId4 = bd[4].getKeyId(); + this._visibility4 = bd[4].visibility; + protoEI.eventEmitterAccessors[4] = bd[4].createEventEmitterAccessors(); + protoEI.hostActionAccessors[4] = bd[4].createHostActionAccessors(); + } + if (length > 5) { + this._binding5 = bd[5].binding; + this._keyId5 = bd[5].getKeyId(); + this._visibility5 = bd[5].visibility; + protoEI.eventEmitterAccessors[5] = bd[5].createEventEmitterAccessors(); + protoEI.hostActionAccessors[5] = bd[5].createHostActionAccessors(); + } + if (length > 6) { + this._binding6 = bd[6].binding; + this._keyId6 = bd[6].getKeyId(); + this._visibility6 = bd[6].visibility; + protoEI.eventEmitterAccessors[6] = bd[6].createEventEmitterAccessors(); + protoEI.hostActionAccessors[6] = bd[6].createHostActionAccessors(); + } + if (length > 7) { + this._binding7 = bd[7].binding; + this._keyId7 = bd[7].getKeyId(); + this._visibility7 = bd[7].visibility; + protoEI.eventEmitterAccessors[7] = bd[7].createEventEmitterAccessors(); + protoEI.hostActionAccessors[7] = bd[7].createHostActionAccessors(); + } + if (length > 8) { + this._binding8 = bd[8].binding; + this._keyId8 = bd[8].getKeyId(); + this._visibility8 = bd[8].visibility; + protoEI.eventEmitterAccessors[8] = bd[8].createEventEmitterAccessors(); + protoEI.hostActionAccessors[8] = bd[8].createHostActionAccessors(); + } + if (length > 9) { + this._binding9 = bd[9].binding; + this._keyId9 = bd[9].getKeyId(); + this._visibility9 = bd[9].visibility; + protoEI.eventEmitterAccessors[9] = bd[9].createEventEmitterAccessors(); + protoEI.hostActionAccessors[9] = bd[9].createHostActionAccessors(); + } + } + + hasBindings(): boolean { return isPresent(this._binding0); } + + getBindingAtIndex(index: number): any { if (index == 0) return this._binding0; if (index == 1) return this._binding1; if (index == 2) return this._binding2; @@ -648,27 +623,60 @@ export class ProtoElementInjector { if (index == 9) return this._binding9; throw new OutOfBoundsAccess(index); } + + createElementInjectorStrategy(ei: ElementInjector) { + return new ElementInjectorInlineStrategy(this, ei); + } +} + +/** + * Strategy used by the `ProtoElementInjector` when the number of bindings is more than 10. + */ +// TODO(vicb): add an interface +class ProtoElementInjectorDynamicStrategy { + // only _bindings[0] can contain a component + _bindings: List; + _keyIds: List; + _visibilities: List; + + constructor(protoInj: ProtoElementInjector, bd: List) { + var len = bd.length; + + this._bindings = ListWrapper.createFixedSize(len); + this._keyIds = ListWrapper.createFixedSize(len); + this._visibilities = ListWrapper.createFixedSize(len); + + for (var i = 0; i < len; i++) { + this._bindings[i] = bd[i].binding; + this._keyIds[i] = bd[i].getKeyId(); + this._visibilities[i] = bd[i].visibility; + protoInj.eventEmitterAccessors[i] = bd[i].createEventEmitterAccessors(); + protoInj.hostActionAccessors[i] = bd[i].createHostActionAccessors(); + } + } + + hasBindings(): boolean { return isPresent(this._bindings[0]); } + + getBindingAtIndex(index: number): any { + if (index < 0 || index >= this._bindings.length) { + throw new OutOfBoundsAccess(index); + } + + return this._bindings[index]; + } + + createElementInjectorStrategy(ei: ElementInjector) { + return new ElementInjectorDynamicStrategy(this, ei); + } } export class ElementInjector extends TreeNode { - private _proto: ProtoElementInjector; - private _lightDomAppInjector: Injector; - private _shadowDomAppInjector: Injector; + private _lightDomAppInjector: Injector = null; + private _shadowDomAppInjector: Injector = null; private _host: ElementInjector; - // If this element injector has a component, the component instance will be stored in _obj0 - private _obj0: any; - private _obj1: any; - private _obj2: any; - private _obj3: any; - private _obj4: any; - private _obj5: any; - private _obj6: any; - private _obj7: any; - private _obj8: any; - private _obj9: any; - private _preBuiltObjects; - private _constructionCounter; + private _preBuiltObjects = null; + private _constructionCounter: number = 0; private _dynamicallyCreatedComponent: any; private _dynamicallyCreatedComponentBinding: DirectiveBinding; @@ -679,24 +687,12 @@ export class ElementInjector extends TreeNode { private _query1: QueryRef; private _query2: QueryRef; - constructor(proto: ProtoElementInjector, parent: ElementInjector) { - super(parent); - this._proto = proto; + _strategy; + + constructor(public _proto: ProtoElementInjector, parent: ElementInjector) { + super(parent); + this._strategy = _proto._strategy.createElementInjectorStrategy(this); - // we cannot call dehydrate because fields won't be detected - this._preBuiltObjects = null; - this._lightDomAppInjector = null; - this._shadowDomAppInjector = null; - this._obj0 = null; - this._obj1 = null; - this._obj2 = null; - this._obj3 = null; - this._obj4 = null; - this._obj5 = null; - this._obj6 = null; - this._obj7 = null; - this._obj8 = null; - this._obj9 = null; this._constructionCounter = 0; this._inheritQueries(parent); @@ -709,53 +705,15 @@ export class ElementInjector extends TreeNode { this._lightDomAppInjector = null; this._shadowDomAppInjector = null; - var p = this._proto; + this._strategy.callOnDestroy(); - if (p._binding0 instanceof DirectiveBinding && (p._binding0).callOnDestroy) { - this._obj0.onDestroy(); - } - if (p._binding1 instanceof DirectiveBinding && (p._binding1).callOnDestroy) { - this._obj1.onDestroy(); - } - if (p._binding2 instanceof DirectiveBinding && (p._binding2).callOnDestroy) { - this._obj2.onDestroy(); - } - if (p._binding3 instanceof DirectiveBinding && (p._binding3).callOnDestroy) { - this._obj3.onDestroy(); - } - if (p._binding4 instanceof DirectiveBinding && (p._binding4).callOnDestroy) { - this._obj4.onDestroy(); - } - if (p._binding5 instanceof DirectiveBinding && (p._binding5).callOnDestroy) { - this._obj5.onDestroy(); - } - if (p._binding6 instanceof DirectiveBinding && (p._binding6).callOnDestroy) { - this._obj6.onDestroy(); - } - if (p._binding7 instanceof DirectiveBinding && (p._binding7).callOnDestroy) { - this._obj7.onDestroy(); - } - if (p._binding8 instanceof DirectiveBinding && (p._binding8).callOnDestroy) { - this._obj8.onDestroy(); - } - if (p._binding9 instanceof DirectiveBinding && (p._binding9).callOnDestroy) { - this._obj9.onDestroy(); - } if (isPresent(this._dynamicallyCreatedComponentBinding) && this._dynamicallyCreatedComponentBinding.callOnDestroy) { this._dynamicallyCreatedComponent.onDestroy(); } - this._obj0 = null; - this._obj1 = null; - this._obj2 = null; - this._obj3 = null; - this._obj4 = null; - this._obj5 = null; - this._obj6 = null; - this._obj7 = null; - this._obj8 = null; - this._obj9 = null; + this._strategy.clearInstances(); + this._dynamicallyCreatedComponent = null; this._dynamicallyCreatedComponentBinding = null; @@ -772,21 +730,12 @@ export class ElementInjector extends TreeNode { if (p._firstBindingIsComponent) { this._shadowDomAppInjector = - this._createShadowDomAppInjector(p._binding0, injector); + this._createShadowDomAppInjector(this._strategy.getComponentBinding(), injector); } this._checkShadowDomAppInjector(this._shadowDomAppInjector); - 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); + this._strategy.hydrate(); } private _createShadowDomAppInjector(componentDirective: DirectiveBinding, appInjector: Injector) { @@ -829,14 +778,18 @@ export class ElementInjector extends TreeNode { } hasDirective(type: Type): boolean { - return this._getObjByKeyId(Key.get(type).id, LIGHT_DOM_AND_SHADOW_DOM) !== _undefined; + return this._strategy.getObjByKeyId(Key.get(type).id, LIGHT_DOM_AND_SHADOW_DOM) !== _undefined; } - getEventEmitterAccessors() { return this._proto.eventEmitterAccessors; } + getEventEmitterAccessors(): List> { + return this._proto.eventEmitterAccessors; + } - getHostActionAccessors() { return this._proto.hostActionAccessors; } + getHostActionAccessors(): List> { + return this._proto.hostActionAccessors; + } - getComponent() { return this._obj0; } + getComponent(): any { return this._strategy.getComponent(); } getElementRef() { return new ElementRef(new ViewRef(this._preBuiltObjects.view), this._proto.index); @@ -850,17 +803,15 @@ export class ElementInjector extends TreeNode { directParent(): ElementInjector { return this._proto.distanceToParent < 2 ? this.parent : null; } - private _isComponentKey(key: Key) { - return this._proto._firstBindingIsComponent && isPresent(key) && key.id === this._proto._keyId0; - } + private _isComponentKey(key: Key) { return this._strategy.isComponentKey(key); } private _isDynamicallyLoadedComponentKey(key: Key) { return isPresent(this._dynamicallyCreatedComponentBinding) && key.id === this._dynamicallyCreatedComponentBinding.key.id; } - private _new(binding: ResolvedBinding) { - if (this._constructionCounter++ > _MAX_DIRECTIVE_CONSTRUCTION_COUNTER) { + _new(binding: ResolvedBinding): any { + if (this._constructionCounter++ > this._strategy.getMaxDirectives()) { throw new CyclicDependencyError(binding.key); } @@ -920,8 +871,6 @@ export class ElementInjector extends TreeNode { case 10: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9); break; - default: - throw `Directive ${binding.key.token} can only have up to 10 dependencies.`; } this._addToQueries(obj, binding.key.token); @@ -961,7 +910,7 @@ export class ElementInjector extends TreeNode { return this._getByKey(dirDep.key, dirDep.visibility, dirDep.optional, requestor); } - private _buildAttribute(dep): string { + private _buildAttribute(dep: DirectiveDependency): string { var attributes = this._proto.attributes; if (isPresent(attributes) && MapWrapper.contains(attributes, dep.attributeName)) { return MapWrapper.get(attributes, dep.attributeName); @@ -970,7 +919,7 @@ export class ElementInjector extends TreeNode { } } - private _buildQueriesForDeps(deps: List) { + _buildQueriesForDeps(deps: List): void { for (var i = 0; i < deps.length; i++) { var dep = deps[i]; if (isPresent(dep.queryDirective)) { @@ -979,7 +928,7 @@ export class ElementInjector extends TreeNode { } } - private _createQueryRef(directive) { + private _createQueryRef(directive): void { var queryList = new QueryList(); if (isBlank(this._query0)) { this._query0 = new QueryRef(directive, queryList, this); @@ -991,7 +940,7 @@ export class ElementInjector extends TreeNode { throw new QueryError(); } - private _addToQueries(obj, token) { + private _addToQueries(obj, token): void { if (isPresent(this._query0) && (this._query0.directive === token)) { this._query0.list.add(obj); } @@ -1017,42 +966,13 @@ export class ElementInjector extends TreeNode { } } - private _buildQueries() { - if (isBlank(this._proto)) return; - var p = this._proto; - if (p._binding0 instanceof DirectiveBinding) { - this._buildQueriesForDeps(>p._binding0.dependencies); - } - if (p._binding1 instanceof DirectiveBinding) { - this._buildQueriesForDeps(>p._binding1.dependencies); - } - if (p._binding2 instanceof DirectiveBinding) { - this._buildQueriesForDeps(>p._binding2.dependencies); - } - if (p._binding3 instanceof DirectiveBinding) { - this._buildQueriesForDeps(>p._binding3.dependencies); - } - if (p._binding4 instanceof DirectiveBinding) { - this._buildQueriesForDeps(>p._binding4.dependencies); - } - if (p._binding5 instanceof DirectiveBinding) { - this._buildQueriesForDeps(>p._binding5.dependencies); - } - if (p._binding6 instanceof DirectiveBinding) { - this._buildQueriesForDeps(>p._binding6.dependencies); - } - if (p._binding7 instanceof DirectiveBinding) { - this._buildQueriesForDeps(>p._binding7.dependencies); - } - if (p._binding8 instanceof DirectiveBinding) { - this._buildQueriesForDeps(>p._binding8.dependencies); - } - if (p._binding9 instanceof DirectiveBinding) { - this._buildQueriesForDeps(>p._binding9.dependencies); + private _buildQueries(): void { + if (isPresent(this._proto)) { + this._strategy.buildQueries(); } } - private _findQuery(token) { + private _findQuery(token): QueryRef { if (isPresent(this._query0) && this._query0.directive === token) { return this._query0; } @@ -1065,17 +985,17 @@ export class ElementInjector extends TreeNode { throw new BaseException(`Cannot find query for directive ${token}.`); } - link(parent: ElementInjector) { + link(parent: ElementInjector): void { parent.addChild(this); this._addParentQueries(); } - linkAfter(parent: ElementInjector, prevSibling: ElementInjector) { + linkAfter(parent: ElementInjector, prevSibling: ElementInjector): void { parent.addChildAfter(this, prevSibling); this._addParentQueries(); } - private _addParentQueries() { + private _addParentQueries(): void { if (isPresent(this.parent._query0)) { this._addQueryToTree(this.parent._query0); this.parent._query0.update(); @@ -1090,7 +1010,7 @@ export class ElementInjector extends TreeNode { } } - unlink() { + unlink(): void { var queriesToUpDate = []; if (isPresent(this.parent._query0)) { this._pruneQueryFromTree(this.parent._query0); @@ -1110,8 +1030,7 @@ export class ElementInjector extends TreeNode { ListWrapper.forEach(queriesToUpDate, (q) => q.update()); } - - private _pruneQueryFromTree(query: QueryRef) { + private _pruneQueryFromTree(query: QueryRef): void { this._removeQueryRef(query); var child = this._head; @@ -1121,7 +1040,7 @@ export class ElementInjector extends TreeNode { } } - private _addQueryToTree(query: QueryRef) { + private _addQueryToTree(query: QueryRef): void { this._assignQueryRef(query); var child = this._head; @@ -1131,7 +1050,7 @@ export class ElementInjector extends TreeNode { } } - private _assignQueryRef(query: QueryRef) { + private _assignQueryRef(query: QueryRef): void { if (isBlank(this._query0)) { this._query0 = query; return; @@ -1145,13 +1064,13 @@ export class ElementInjector extends TreeNode { throw new QueryError(); } - private _removeQueryRef(query: QueryRef) { + private _removeQueryRef(query: QueryRef): void { if (this._query0 == query) this._query0 = null; if (this._query1 == query) this._query1 = null; if (this._query2 == query) this._query2 = null; } - private _getByKey(key: Key, visibility: Visibility, optional: boolean, requestor: Key) { + private _getByKey(key: Key, visibility: Visibility, optional: boolean, requestor: Key): any { var ei = this; var currentVisibility = this._isComponentKey(requestor) ? @@ -1203,7 +1122,7 @@ export class ElementInjector extends TreeNode { } } - private _appInjector(requestor: Key) { + private _appInjector(requestor: Key): Injector { if (isPresent(requestor) && (this._isComponentKey(requestor) || this._isDynamicallyLoadedComponentKey(requestor))) { return this._shadowDomAppInjector; @@ -1212,74 +1131,224 @@ export class ElementInjector extends TreeNode { } } - private _getPreBuiltObjectByKeyId(keyId: int) { + private _getPreBuiltObjectByKeyId(keyId: number): any { var staticKeys = StaticKeys.instance(); if (keyId === staticKeys.viewManagerId) return this._preBuiltObjects.viewManager; - // TODO add other objects as needed return _undefined; } - private _getObjByKeyId(keyId: int, visibility: number) { - var p = this._proto; + private _getObjByKeyId(keyId: number, visibility: number) { + return this._strategy.getObjByKeyId(keyId, visibility); + } + + getDirectiveAtIndex(index: number) { return this._strategy.getDirectiveAtIndex(index); } + + hasInstances(): boolean { return this._constructionCounter > 0; } + + /** Gets whether this element is exporting a component instance as $implicit. */ + isExportingComponent(): boolean { return this._proto.exportComponent; } + + /** Gets whether this element is exporting its element as $implicit. */ + isExportingElement(): boolean { return this._proto.exportElement; } + + /** Get the name to which this element's $implicit is to be assigned. */ + getExportImplicitName(): string { return this._proto.exportImplicitName; } + + getLightDomAppInjector(): Injector { return this._lightDomAppInjector; } + + getShadowDomAppInjector(): Injector { return this._shadowDomAppInjector; } + + getHost(): ElementInjector { return this._host; } + + getBoundElementIndex(): number { return this._proto.index; } +} + +/** + * Strategy used by the `ElementInjector` when the number of bindings is 10 or less. + * In such a case, inlining fields is benefitial for performances. + */ +// TODO(vicb): add an interface +class ElementInjectorInlineStrategy { + // If this element injector has a component, the component instance will be stored in _obj0 + _obj0: any = null; + _obj1: any = null; + _obj2: any = null; + _obj3: any = null; + _obj4: any = null; + _obj5: any = null; + _obj6: any = null; + _obj7: any = null; + _obj8: any = null; + _obj9: any = null; + + constructor(public _protoStrategy: ProtoElementInjectorInlineStrategy, + public _ei: ElementInjector) {} + + callOnDestroy(): void { + var p = this._protoStrategy; + + if (p._binding0 instanceof DirectiveBinding && (p._binding0).callOnDestroy) { + this._obj0.onDestroy(); + } + if (p._binding1 instanceof DirectiveBinding && (p._binding1).callOnDestroy) { + this._obj1.onDestroy(); + } + if (p._binding2 instanceof DirectiveBinding && (p._binding2).callOnDestroy) { + this._obj2.onDestroy(); + } + if (p._binding3 instanceof DirectiveBinding && (p._binding3).callOnDestroy) { + this._obj3.onDestroy(); + } + if (p._binding4 instanceof DirectiveBinding && (p._binding4).callOnDestroy) { + this._obj4.onDestroy(); + } + if (p._binding5 instanceof DirectiveBinding && (p._binding5).callOnDestroy) { + this._obj5.onDestroy(); + } + if (p._binding6 instanceof DirectiveBinding && (p._binding6).callOnDestroy) { + this._obj6.onDestroy(); + } + if (p._binding7 instanceof DirectiveBinding && (p._binding7).callOnDestroy) { + this._obj7.onDestroy(); + } + if (p._binding8 instanceof DirectiveBinding && (p._binding8).callOnDestroy) { + this._obj8.onDestroy(); + } + if (p._binding9 instanceof DirectiveBinding && (p._binding9).callOnDestroy) { + this._obj9.onDestroy(); + } + } + + clearInstances(): void { + this._obj0 = null; + this._obj1 = null; + this._obj2 = null; + this._obj3 = null; + this._obj4 = null; + this._obj5 = null; + this._obj6 = null; + this._obj7 = null; + this._obj8 = null; + this._obj9 = null; + } + + hydrate(): void { + var p = this._protoStrategy; + + 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); + } + + getComponent(): any { return this._obj0; } + + isComponentKey(key: Key) { + return this._ei._proto._firstBindingIsComponent && isPresent(key) && + key.id === this._protoStrategy._keyId0; + } + + buildQueries(): void { + var p = this._protoStrategy; + if (p._binding0 instanceof DirectiveBinding) { + this._ei._buildQueriesForDeps(>p._binding0.dependencies); + } + if (p._binding1 instanceof DirectiveBinding) { + this._ei._buildQueriesForDeps(>p._binding1.dependencies); + } + if (p._binding2 instanceof DirectiveBinding) { + this._ei._buildQueriesForDeps(>p._binding2.dependencies); + } + if (p._binding3 instanceof DirectiveBinding) { + this._ei._buildQueriesForDeps(>p._binding3.dependencies); + } + if (p._binding4 instanceof DirectiveBinding) { + this._ei._buildQueriesForDeps(>p._binding4.dependencies); + } + if (p._binding5 instanceof DirectiveBinding) { + this._ei._buildQueriesForDeps(>p._binding5.dependencies); + } + if (p._binding6 instanceof DirectiveBinding) { + this._ei._buildQueriesForDeps(>p._binding6.dependencies); + } + if (p._binding7 instanceof DirectiveBinding) { + this._ei._buildQueriesForDeps(>p._binding7.dependencies); + } + if (p._binding8 instanceof DirectiveBinding) { + this._ei._buildQueriesForDeps(>p._binding8.dependencies); + } + if (p._binding9 instanceof DirectiveBinding) { + this._ei._buildQueriesForDeps(>p._binding9.dependencies); + } + } + + getObjByKeyId(keyId: number, visibility: number): any { + var p = this._protoStrategy; if (p._keyId0 === keyId && (p._visibility0 & visibility) > 0) { if (isBlank(this._obj0)) { - this._obj0 = this._new(p._binding0); + this._obj0 = this._ei._new(p._binding0); } return this._obj0; } if (p._keyId1 === keyId && (p._visibility1 & visibility) > 0) { if (isBlank(this._obj1)) { - this._obj1 = this._new(p._binding1); + this._obj1 = this._ei._new(p._binding1); } return this._obj1; } if (p._keyId2 === keyId && (p._visibility2 & visibility) > 0) { if (isBlank(this._obj2)) { - this._obj2 = this._new(p._binding2); + this._obj2 = this._ei._new(p._binding2); } return this._obj2; } if (p._keyId3 === keyId && (p._visibility3 & visibility) > 0) { if (isBlank(this._obj3)) { - this._obj3 = this._new(p._binding3); + this._obj3 = this._ei._new(p._binding3); } return this._obj3; } if (p._keyId4 === keyId && (p._visibility4 & visibility) > 0) { if (isBlank(this._obj4)) { - this._obj4 = this._new(p._binding4); + this._obj4 = this._ei._new(p._binding4); } return this._obj4; } if (p._keyId5 === keyId && (p._visibility5 & visibility) > 0) { if (isBlank(this._obj5)) { - this._obj5 = this._new(p._binding5); + this._obj5 = this._ei._new(p._binding5); } return this._obj5; } if (p._keyId6 === keyId && (p._visibility6 & visibility) > 0) { if (isBlank(this._obj6)) { - this._obj6 = this._new(p._binding6); + this._obj6 = this._ei._new(p._binding6); } return this._obj6; } if (p._keyId7 === keyId && (p._visibility7 & visibility) > 0) { if (isBlank(this._obj7)) { - this._obj7 = this._new(p._binding7); + this._obj7 = this._ei._new(p._binding7); } return this._obj7; } if (p._keyId8 === keyId && (p._visibility8 & visibility) > 0) { if (isBlank(this._obj8)) { - this._obj8 = this._new(p._binding8); + this._obj8 = this._ei._new(p._binding8); } return this._obj8; } if (p._keyId9 === keyId && (p._visibility9 & visibility) > 0) { if (isBlank(this._obj9)) { - this._obj9 = this._new(p._binding9); + this._obj9 = this._ei._new(p._binding9); } return this._obj9; } @@ -1287,7 +1356,7 @@ export class ElementInjector extends TreeNode { return _undefined; } - getDirectiveAtIndex(index: int) { + getDirectiveAtIndex(index: number) { if (index == 0) return this._obj0; if (index == 1) return this._obj1; if (index == 2) return this._obj2; @@ -1301,24 +1370,93 @@ export class ElementInjector extends TreeNode { throw new OutOfBoundsAccess(index); } - hasInstances() { return this._constructionCounter > 0; } + getComponentBinding(): ResolvedBinding { return this._protoStrategy._binding0; } - /** Gets whether this element is exporting a component instance as $implicit. */ - isExportingComponent() { return this._proto.exportComponent; } + getMaxDirectives(): number { return _MAX_DIRECTIVE_CONSTRUCTION_COUNTER; } +} - /** Gets whether this element is exporting its element as $implicit. */ - isExportingElement() { return this._proto.exportElement; } +/** + * Strategy used by the `ElementInjector` when the number of bindings is 10 or less. + * In such a case, inlining fields is benefitial for performances. + */ +// TODO(vicb): add an interface +class ElementInjectorDynamicStrategy { + // If this element injector has a component, the component instance will be stored in _objs[0] + _objs: List; - /** Get the name to which this element's $implicit is to be assigned. */ - getExportImplicitName() { return this._proto.exportImplicitName; } + constructor(public _protoStrategy: ProtoElementInjectorDynamicStrategy, + public _ei: ElementInjector) { + this._objs = ListWrapper.createFixedSize(_protoStrategy._bindings.length); + } - getLightDomAppInjector() { return this._lightDomAppInjector; } + callOnDestroy(): void { + var p = this._protoStrategy; - getShadowDomAppInjector() { return this._shadowDomAppInjector; } + for (var i = 0; i < p._bindings.length; i++) { + if (p._bindings[i] instanceof DirectiveBinding && + (p._bindings[i]).callOnDestroy) { + this._objs[i].onDestroy(); + } + } + } - getHost() { return this._host; } + clearInstances(): void { ListWrapper.fill(this._objs, null); } - getBoundElementIndex() { return this._proto.index; } + hydrate(): void { + 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); + } + } + } + + getComponent(): any { return this._objs[0]; } + + isComponentKey(key: Key) { + return this._ei._proto._firstBindingIsComponent && isPresent(key) && + key.id === this._protoStrategy._keyIds[0]; + } + + buildQueries(): void { + var p = this._protoStrategy; + + for (var i = 0; i < p._bindings.length; i++) { + if (p._bindings[i] instanceof DirectiveBinding) { + this._ei._buildQueriesForDeps(>p._bindings[i].dependencies); + } + } + } + + getObjByKeyId(keyId: number, visibility: number): any { + var p = this._protoStrategy; + + // TODO(vicb): optimize lookup ? + for (var i = 0; i < p._keyIds.length; i++) { + if (p._keyIds[i] === keyId && (p._visibilities[i] & visibility) > 0) { + if (isBlank(this._objs[i])) { + this._objs[i] = this._ei._new(p._bindings[i]); + } + + return this._objs[i]; + } + } + + return _undefined; + } + + getDirectiveAtIndex(index: number): any { + if (index < 0 || index >= this._objs.length) { + throw new OutOfBoundsAccess(index); + } + + return this._objs[index]; + } + + getComponentBinding(): ResolvedBinding { return this._protoStrategy._bindings[0]; } + + getMaxDirectives(): number { return this._objs.length; } } class OutOfBoundsAccess extends BaseException { @@ -1328,7 +1466,7 @@ class OutOfBoundsAccess extends BaseException { this.message = `Index ${index} is out-of-bounds.`; } - toString() { return this.message; } + toString(): string { return this.message; } } class QueryError extends BaseException { @@ -1339,26 +1477,27 @@ class QueryError extends BaseException { this.message = 'Only 3 queries can be concurrently active in a template.'; } - toString() { return this.message; } + toString(): string { return this.message; } } class QueryRef { directive; list: QueryList; originator: ElementInjector; + constructor(directive, list: QueryList, originator: ElementInjector) { this.directive = directive; this.list = list; this.originator = originator; } - update() { + update(): void { var aggregator = []; this.visit(this.originator, aggregator); this.list.reset(aggregator); } - visit(inj: ElementInjector, aggregator) { + visit(inj: ElementInjector, aggregator): void { if (isBlank(inj)) return; if (inj.hasDirective(this.directive)) { ListWrapper.push(aggregator, inj.get(this.directive)); diff --git a/modules/angular2/test/core/compiler/element_injector_spec.js b/modules/angular2/test/core/compiler/element_injector_spec.js index 9e990eb5c3..5fbf61d8ec 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.js +++ b/modules/angular2/test/core/compiler/element_injector_spec.js @@ -211,6 +211,13 @@ export function main() { var defaultPreBuiltObjects = new PreBuiltObjects(null, null, null); var appInjector = Injector.resolveAndCreate([]); + // An injector with more than 10 bindings will switch to the dynamic strategy + var dynamicBindings = []; + + for (var i = 0; i < 20; i++) { + ListWrapper.push(dynamicBindings, bind(i).toValue(i)); + } + function createPei(parent, index, bindings, distance = 1, hasShadowRoot = false) { var directiveBinding = ListWrapper.map(bindings, b => { if (b instanceof DirectiveBinding) return b; @@ -350,8 +357,6 @@ export function main() { }); }); - - describe("ProtoElementInjector", () => { describe("direct parent", () => { it("should return parent proto injector when distance is 1", () => { @@ -370,10 +375,11 @@ export function main() { expect(protoChild.directParent()).toEqual(null); }); - it("should allow for direct access using getBindingAtIndex", function () { - var binding = DirectiveBinding.createFromBinding( - bind(SimpleDirective).toClass(SimpleDirective), null); - var proto = createPei(null, 0, [binding]); + }); + + describe('inline strategy', () => { + it("should allow for direct access using getBindingAtIndex", () => { + var proto = createPei(null, 0, [bind(SimpleDirective).toClass(SimpleDirective)]); expect(proto.getBindingAtIndex(0)).toBeAnInstanceOf(DirectiveBinding); expect(() => proto.getBindingAtIndex(-1)).toThrowError( @@ -383,6 +389,19 @@ export function main() { }); }); + describe('dynamic strategy', () => { + it("should allow for direct access using getBindingAtIndex", () => { + var proto = createPei(null, 0, dynamicBindings); + + expect(proto.getBindingAtIndex(0)).toBeAnInstanceOf(DirectiveBinding); + expect(() => proto.getBindingAtIndex(-1)).toThrowError( + 'Index -1 is out-of-bounds.'); + expect(() => proto.getBindingAtIndex(dynamicBindings.length - 1)).not.toThrow(); + expect(() => proto.getBindingAtIndex(dynamicBindings.length)).toThrowError( + `Index ${dynamicBindings.length} is out-of-bounds.`); + }); + }); + describe('event emitters', () => { it('should return a list of event accessors', () => { var binding = DirectiveBinding.createFromType( @@ -434,12 +453,21 @@ export function main() { expect(pei.getBindingAtIndex(0).key.token).toBe(SimpleDirective); expect(pei.getBindingAtIndex(1).key.token).toEqual("injectable1"); }); + + it('should support an arbitrary number of bindings', () => { + var pei = createPei(null, 0, dynamicBindings); + + for (var i = 0; i < dynamicBindings.length; i++) { + expect(pei.getBindingAtIndex(i).key.token).toBe(i); + } + }); }); + }); - describe("ElementInjector", function () { - describe("instantiate", function () { - it("should create an element injector", function () { + describe("ElementInjector", () => { + describe("instantiate", () => { + it("should create an element injector", () => { var protoParent = createPei(null, 0, []); var protoChild1 = createPei(protoParent, 1, []); var protoChild2 = createPei(protoParent, 2, []); @@ -480,522 +508,586 @@ export function main() { }); }); - describe("hasBindings", function () { - it("should be true when there are bindings", function () { + describe("hasBindings", () => { + it("should be true when there are bindings", () => { var p = createPei(null, 0, [SimpleDirective]); expect(p.hasBindings).toBeTruthy(); }); - it("should be false otherwise", function () { + it("should be false otherwise", () => { var p = createPei(null, 0, []); expect(p.hasBindings).toBeFalsy(); }); }); - describe("hasInstances", function () { - it("should be false when no directives are instantiated", function () { + describe("hasInstances", () => { + it("should be false when no directives are instantiated", () => { expect(injector([]).hasInstances()).toBe(false); }); - it("should be true when directives are instantiated", function () { + it("should be true when directives are instantiated", () => { expect(injector([SimpleDirective]).hasInstances()).toBe(true); }); }); - describe("hydrate", function () { - it("should instantiate directives that have no dependencies", function () { - var inj = injector([SimpleDirective]); - expect(inj.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective); - }); - - it("should instantiate directives that depend on other directives", function () { - var inj = injector([SimpleDirective, NeedsDirective]); - - var d = inj.get(NeedsDirective); - - expect(d).toBeAnInstanceOf(NeedsDirective); - expect(d.dependency).toBeAnInstanceOf(SimpleDirective); - }); - - it("should instantiate hostInjector injectables that have dependencies", function () { - var inj = injector([ - DirectiveBinding.createFromType(SimpleDirective, - new DummyDirective({hostInjector: [ - bind('injectable1').toValue('injectable1'), - bind('injectable2').toFactory((val) => `${val}-injectable2`, ['injectable1']) - ]})) - ]); - expect(inj.get('injectable2')).toEqual('injectable1-injectable2'); - }); - - it("should instantiate hostInjector injectables that have dependencies with set visibility", function () { - var childInj= parentChildInjectors([ - DirectiveBinding.createFromType(SimpleDirective, - new DummyDirective({hostInjector: [ - bind('injectable1').toValue('injectable1') - ]})) - ], [ - DirectiveBinding.createFromType(SimpleDirective, - new DummyDirective({hostInjector: [ - bind('injectable1').toValue('new-injectable1'), - bind('injectable2').toFactory((val) => `${val}-injectable2`, [[new Inject('injectable1'), new Parent()]]) - ]})) - ]); - expect(childInj.get('injectable2')).toEqual('injectable1-injectable2'); - }); - - it("should instantiate components that depends on viewInjector dependencies", function () { - var inj = injector([ - DirectiveBinding.createFromType(NeedsService, - new DummyDirective({viewInjector: [ - bind('service').toValue('service') - ]})) - ], null, true); - expect(inj.get(NeedsService).service).toEqual('service'); - }); - - it("should instantiate directives that depend on app services", function () { - var appInjector = Injector.resolveAndCreate([ - bind("service").toValue("service") - ]); - var inj = injector([NeedsService], appInjector); - - var d = inj.get(NeedsService); - expect(d).toBeAnInstanceOf(NeedsService); - expect(d.service).toEqual("service"); - }); - - it("should instantiate directives that depend on pre built objects", function () { - var protoView = new AppProtoView(null, null, null); - var inj = injector([NeedsProtoViewRef], null, false, new PreBuiltObjects(null, null, protoView)); - - expect(inj.get(NeedsProtoViewRef).protoViewRef).toEqual(new ProtoViewRef(protoView)); - }); - - it("should return app services", function () { - var appInjector = Injector.resolveAndCreate([ - bind("service").toValue("service") - ]); - var inj = injector([], appInjector); - - expect(inj.get('service')).toEqual('service'); - }); - - it("should get directives from parent", function () { - var child = parentChildInjectors([SimpleDirective], [NeedsDirectiveFromParent]); - - var d = child.get(NeedsDirectiveFromParent); - - expect(d).toBeAnInstanceOf(NeedsDirectiveFromParent); - expect(d.dependency).toBeAnInstanceOf(SimpleDirective); - }); - - it("should not return parent's directives on self", function () { - expect(() => { - injector([SimpleDirective, NeedsDirectiveFromParent]); - }).toThrowError(new RegExp("No provider for SimpleDirective")); - }); - - it("should get directives from ancestor", function () { - var child = parentChildInjectors([SimpleDirective], [NeedsDirectiveFromAncestor]); - - var d = child.get(NeedsDirectiveFromAncestor); - - expect(d).toBeAnInstanceOf(NeedsDirectiveFromAncestor); - expect(d.dependency).toBeAnInstanceOf(SimpleDirective); - }); - - it("should get directives crossing the boundaries", function () { - var child = hostShadowInjectors([SomeOtherDirective, SimpleDirective], - [NeedsDirectiveFromAnAncestorShadowDom]); - - var d = child.get(NeedsDirectiveFromAnAncestorShadowDom); - - expect(d).toBeAnInstanceOf(NeedsDirectiveFromAnAncestorShadowDom); - expect(d.dependency).toBeAnInstanceOf(SimpleDirective); - }); - - it("should throw when a depenency cannot be resolved", function () { - expect(() => injector([NeedsDirectiveFromParent])). - toThrowError('No provider for SimpleDirective! (NeedsDirectiveFromParent -> SimpleDirective)'); - }); - - it("should inject null when an optional dependency cannot be resolved", function () { - var inj = injector([OptionallyNeedsDirective]); - var d = inj.get(OptionallyNeedsDirective); - expect(d.dependency).toEqual(null); - }); - - it("should accept bindings instead types", function () { - var inj = injector([ - DirectiveBinding.createFromBinding(bind(SimpleDirective).toClass(SimpleDirective), null) - ]); - expect(inj.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective); - }); - - it("should allow for direct access using getDirectiveAtIndex", function () { - var inj = injector([ - DirectiveBinding.createFromBinding(bind(SimpleDirective).toClass(SimpleDirective), null) - ]); - expect(inj.getDirectiveAtIndex(0)).toBeAnInstanceOf(SimpleDirective); - expect(() => inj.getDirectiveAtIndex(-1)).toThrowError( - 'Index -1 is out-of-bounds.'); - expect(() => inj.getDirectiveAtIndex(10)).toThrowError( - 'Index 10 is out-of-bounds.'); - }); - - - it("should handle cyclic dependencies", function () { - expect(() => { - var bAneedsB = bind(A_Needs_B).toFactory((a) => new A_Needs_B(a), [B_Needs_A]); - var bBneedsA = bind(B_Needs_A).toFactory((a) => new B_Needs_A(a), [A_Needs_B]); - injector([ - DirectiveBinding.createFromBinding(bAneedsB, null), - DirectiveBinding.createFromBinding(bBneedsA, null) - ]); - }).toThrowError('Cannot instantiate cyclic dependency! ' + - '(A_Needs_B -> B_Needs_A -> A_Needs_B)'); - }); - - describe("shadow DOM components", () => { - it("should instantiate directives that depend on the containing component", function () { - var directiveBinding = DirectiveBinding.createFromType(SimpleDirective, new Component()); - var shadow = hostShadowInjectors([directiveBinding], [NeedsDirective]); - - var d = shadow.get(NeedsDirective); - expect(d).toBeAnInstanceOf(NeedsDirective); - expect(d.dependency).toBeAnInstanceOf(SimpleDirective); - }); - - it("should not instantiate directives that depend on other directives in the containing component's ElementInjector", () => { - var directiveBinding = DirectiveBinding.createFromType(SomeOtherDirective, new Component()); - expect(() => { - hostShadowInjectors([directiveBinding, SimpleDirective],[NeedsDirective]); - }).toThrowError('No provider for SimpleDirective! (NeedsDirective -> SimpleDirective)'); - }); - - it("should instantiate component directives that depend on app services in the shadow app injector", () => { - var directiveAnnotation = new Component({ - appInjector: [ - bind("service").toValue("service") - ] + [ + { strategy: 'inline', bindings: []}, + { strategy: 'dynamic', bindings: dynamicBindings} + ].forEach((context) => { + var extraBindings = context['bindings']; + describe(`${context['strategy']} strategy`, () => { + describe("hydrate", () => { + it("should instantiate directives that have no dependencies", () => { + var bindings = ListWrapper.concat([SimpleDirective], extraBindings); + var inj = injector(bindings); + expect(inj.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective); }); - var componentDirective = DirectiveBinding.createFromType( - NeedsService, directiveAnnotation); - var inj = injector([componentDirective], null, true); - var d = inj.get(NeedsService); - expect(d).toBeAnInstanceOf(NeedsService); - expect(d.service).toEqual("service"); - }); + it("should instantiate directives that depend on an arbitrary number of directives", () => { + var bindings = ListWrapper.concat([SimpleDirective, NeedsDirective], extraBindings); + var inj = injector(bindings); - it("should not instantiate other directives that depend on app services in the shadow app injector", () => { - var directiveAnnotation = new Component({ - appInjector: [ - bind("service").toValue("service") - ] + var d = inj.get(NeedsDirective); + + expect(d).toBeAnInstanceOf(NeedsDirective); + expect(d.dependency).toBeAnInstanceOf(SimpleDirective); + }); + + + it("should instantiate hostInjector injectables that have dependencies with set visibility", function () { + var childInj= parentChildInjectors(ListWrapper.concat([ + DirectiveBinding.createFromType(SimpleDirective, + new DummyDirective({hostInjector: [ + bind('injectable1').toValue('injectable1') + ]})) + ], extraBindings), [ + DirectiveBinding.createFromType(SimpleDirective, + new DummyDirective({hostInjector: [ + bind('injectable1').toValue('new-injectable1'), + bind('injectable2').toFactory((val) => `${val}-injectable2`, [[new Inject('injectable1'), new Parent()]]) + ]})) + ]); + expect(childInj.get('injectable2')).toEqual('injectable1-injectable2'); + }); + + it("should instantiate components that depends on viewInjector dependencies", function () { + var inj = injector([ + DirectiveBinding.createFromType(NeedsService, + new DummyDirective({viewInjector: [ + bind('service').toValue('service') + ]})) + ], null, true); + expect(inj.get(NeedsService).service).toEqual('service'); + }); + + it("should instantiate hostInjector injectables that have dependencies", () => { + var inj = injector(ListWrapper.concat([ + DirectiveBinding.createFromType(SimpleDirective, + new DummyDirective({hostInjector: [ + bind('injectable1').toValue('injectable1'), + bind('injectable2').toFactory((val) => `${val}-injectable2`, ['injectable1']) + ]}))], + extraBindings + )); + expect(inj.get('injectable2')).toEqual('injectable1-injectable2'); + }); + + it("should instantiate components that depends on viewInjector dependencies", function () { + var inj = injector(ListWrapper.concat([ + DirectiveBinding.createFromType(NeedsService, + new DummyDirective({viewInjector: [ + bind('service').toValue('service') + ]}))], + extraBindings), + null, true); + expect(inj.get(NeedsService).service).toEqual('service'); + }); + + it("should instantiate directives that depend on app services", () => { + var appInjector = Injector.resolveAndCreate(ListWrapper.concat([ + bind("service").toValue("service")], extraBindings)); + var inj = injector([NeedsService], appInjector); + + var d = inj.get(NeedsService); + expect(d).toBeAnInstanceOf(NeedsService); + expect(d.service).toEqual("service"); + }); + + it("should instantiate directives that depend on pre built objects", () => { + var protoView = new AppProtoView(null, null, null); + var bindings = ListWrapper.concat([NeedsProtoViewRef], extraBindings); + var inj = injector(bindings, null, false, new PreBuiltObjects(null, null, protoView)); + + expect(inj.get(NeedsProtoViewRef).protoViewRef).toEqual(new ProtoViewRef(protoView)); + }); + + it("should return app services", () => { + var appInjector = Injector.resolveAndCreate(ListWrapper.concat([ + bind("service").toValue("service")], extraBindings + )); + var inj = injector([], appInjector); + + expect(inj.get('service')).toEqual('service'); + }); + + it("should get directives from parent", () => { + var child = parentChildInjectors( + ListWrapper.concat([SimpleDirective], extraBindings), + [NeedsDirectiveFromParent]); + + var d = child.get(NeedsDirectiveFromParent); + + expect(d).toBeAnInstanceOf(NeedsDirectiveFromParent); + expect(d.dependency).toBeAnInstanceOf(SimpleDirective); + }); + + it("should not return parent's directives on self", () => { + expect(() => { + injector(ListWrapper.concat( + [SimpleDirective, NeedsDirectiveFromParent], extraBindings)); + }).toThrowError(new RegExp("No provider for SimpleDirective")); + }); + + it("should get directives from ancestor", () => { + var child = parentChildInjectors( + ListWrapper.concat([SimpleDirective], extraBindings), + [NeedsDirectiveFromAncestor]); + + var d = child.get(NeedsDirectiveFromAncestor); + + expect(d).toBeAnInstanceOf(NeedsDirectiveFromAncestor); + expect(d.dependency).toBeAnInstanceOf(SimpleDirective); + }); + + it("should get directives crossing the boundaries", () => { + var child = hostShadowInjectors( + ListWrapper.concat([SomeOtherDirective, SimpleDirective], extraBindings), + [NeedsDirectiveFromAnAncestorShadowDom]); + + var d = child.get(NeedsDirectiveFromAnAncestorShadowDom); + + expect(d).toBeAnInstanceOf(NeedsDirectiveFromAnAncestorShadowDom); + expect(d.dependency).toBeAnInstanceOf(SimpleDirective); + }); + + it("should throw when a depenency cannot be resolved", () => { + expect(() => injector(ListWrapper.concat([NeedsDirectiveFromParent], extraBindings))). + toThrowError('No provider for SimpleDirective! (NeedsDirectiveFromParent -> SimpleDirective)'); + }); + + it("should inject null when an optional dependency cannot be resolved", () => { + var inj = injector(ListWrapper.concat([OptionallyNeedsDirective], extraBindings)); + var d = inj.get(OptionallyNeedsDirective); + expect(d.dependency).toEqual(null); + }); + + it("should accept bindings instead types", () => { + var inj = injector(ListWrapper.concat([ + bind(SimpleDirective).toClass(SimpleDirective)], + extraBindings + )); + expect(inj.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective); + }); + + it("should allow for direct access using getDirectiveAtIndex", () => { + var bindings = ListWrapper.concat([ + bind(SimpleDirective).toClass(SimpleDirective)], + extraBindings + ); + + var inj = injector(bindings); + + var firsIndexOut = bindings.length > 10 ? bindings.length : 10; + + expect(inj.getDirectiveAtIndex(0)).toBeAnInstanceOf(SimpleDirective); + expect(() => inj.getDirectiveAtIndex(-1)).toThrowError( + 'Index -1 is out-of-bounds.'); + expect(() => inj.getDirectiveAtIndex(firsIndexOut)).toThrowError( + `Index ${firsIndexOut} is out-of-bounds.`); + }); + + + it("should handle cyclic dependencies", () => { + expect(() => { + var bAneedsB = bind(A_Needs_B).toFactory((a) => new A_Needs_B(a), [B_Needs_A]); + var bBneedsA = bind(B_Needs_A).toFactory((a) => new B_Needs_A(a), [A_Needs_B]); + + injector(ListWrapper.concat([bAneedsB, bBneedsA], extraBindings)); + }).toThrowError('Cannot instantiate cyclic dependency! ' + + '(A_Needs_B -> B_Needs_A -> A_Needs_B)'); + }); + + describe("shadow DOM components", () => { + it("should instantiate directives that depend on the containing component", () => { + var directiveBinding = DirectiveBinding.createFromType(SimpleDirective, new Component()); + var shadow = hostShadowInjectors( + ListWrapper.concat([directiveBinding], extraBindings), + [NeedsDirective]); + + var d = shadow.get(NeedsDirective); + expect(d).toBeAnInstanceOf(NeedsDirective); + expect(d.dependency).toBeAnInstanceOf(SimpleDirective); + }); + + it("should not instantiate directives that depend on other directives in the containing component's ElementInjector", () => { + var directiveBinding = DirectiveBinding.createFromType(SomeOtherDirective, new Component()); + expect(() => { + hostShadowInjectors( + ListWrapper.concat([directiveBinding, SimpleDirective], extraBindings), + [NeedsDirective]); + }).toThrowError('No provider for SimpleDirective! (NeedsDirective -> SimpleDirective)'); + }); + + it("should instantiate component directives that depend on app services in the shadow app injector", () => { + var directiveAnnotation = new Component({ + appInjector: ListWrapper.concat([ + bind("service").toValue("service")], + extraBindings + ) + }); + var componentDirective = DirectiveBinding.createFromType( + NeedsService, directiveAnnotation); + var inj = injector([componentDirective], null, true); + + var d = inj.get(NeedsService); + expect(d).toBeAnInstanceOf(NeedsService); + expect(d.service).toEqual("service"); + }); + + it("should not instantiate other directives that depend on app services in the shadow app injector", () => { + var directiveAnnotation = new Component({ + appInjector: ListWrapper.concat([ + bind("service").toValue("service")], + extraBindings + ) + }); + var componentDirective = DirectiveBinding.createFromType(SimpleDirective, directiveAnnotation); + expect(() => { + injector([componentDirective, NeedsService], null); + }).toThrowError('No provider for service! (NeedsService -> service)'); + }); + }); + }); + + describe("lifecycle", () => { + it("should call onDestroy on directives subscribed to this event", function() { + var inj = injector(ListWrapper.concat([ + DirectiveBinding.createFromType(DirectiveWithDestroy, new DummyDirective({lifecycle: [onDestroy]}))], + extraBindings + )); + var destroy = inj.get(DirectiveWithDestroy); + inj.dehydrate(); + expect(destroy.onDestroyCounter).toBe(1); + }); + + it("should work with services", function() { + var inj = injector(ListWrapper.concat([ + DirectiveBinding.createFromType(SimpleDirective, new DummyDirective({hostInjector: [SimpleService]}))], + extraBindings + )); + inj.dehydrate(); + }); + }); + + describe("dynamicallyCreateComponent", () => { + it("should create a component dynamically", () => { + var inj = injector(extraBindings); + + inj.dynamicallyCreateComponent(DirectiveBinding.createFromType(SimpleDirective, null), appInjector); + expect(inj.getDynamicallyLoadedComponent()).toBeAnInstanceOf(SimpleDirective); + expect(inj.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective); + }); + + it("should inject parent dependencies into the dynamically-loaded component", () => { + var inj = parentChildInjectors( + ListWrapper.concat([SimpleDirective], extraBindings), + []); + inj.dynamicallyCreateComponent(DirectiveBinding.createFromType(NeedsDirectiveFromAncestor, null), appInjector); + expect(inj.getDynamicallyLoadedComponent()).toBeAnInstanceOf(NeedsDirectiveFromAncestor); + expect(inj.getDynamicallyLoadedComponent().dependency).toBeAnInstanceOf(SimpleDirective); + }); + + it("should not inject the proxy component into the children of the dynamically-loaded component", () => { + var injWithDynamicallyLoadedComponent = injector([SimpleDirective]); + injWithDynamicallyLoadedComponent.dynamicallyCreateComponent(DirectiveBinding.createFromType(SomeOtherDirective, null), appInjector); + + var shadowDomProtoInjector = createPei( + null, 0, ListWrapper.concat([NeedsDirectiveFromAncestor], extraBindings)); + var shadowDomInj = shadowDomProtoInjector.instantiate(null); + + expect(() => + shadowDomInj.hydrate(appInjector, injWithDynamicallyLoadedComponent, defaultPreBuiltObjects)). + toThrowError(new RegExp("No provider for SimpleDirective")); + }); + + it("should not inject the dynamically-loaded component into directives on the same element", () => { + var dynamicComp = DirectiveBinding.createFromType(SomeOtherDirective, new Component()); + var proto = createPei( + null, 0, ListWrapper.concat([dynamicComp, NeedsDirective], extraBindings), 1, true); + var inj = proto.instantiate(null); + inj.dynamicallyCreateComponent( + DirectiveBinding.createFromType(SimpleDirective, null), appInjector); + + var error = null; + try { + inj.hydrate(Injector.resolveAndCreate([]), null, null); + } catch(e) { + error = e; + } + + expect(error.message).toEqual("No provider for SimpleDirective! (NeedsDirective -> SimpleDirective)"); + }); + + it("should inject the dynamically-loaded component into the children of the dynamically-loaded component", () => { + var componentDirective = DirectiveBinding.createFromType(SimpleDirective, null); + var injWithDynamicallyLoadedComponent = injector([]); + injWithDynamicallyLoadedComponent.dynamicallyCreateComponent(componentDirective, appInjector); + + var shadowDomProtoInjector = createPei( + null, 0, ListWrapper.concat([NeedsDirectiveFromAncestor], extraBindings)); + var shadowDomInjector = shadowDomProtoInjector.instantiate(null); + shadowDomInjector.hydrate(appInjector, injWithDynamicallyLoadedComponent, defaultPreBuiltObjects); + + expect(shadowDomInjector.get(NeedsDirectiveFromAncestor)).toBeAnInstanceOf(NeedsDirectiveFromAncestor); + expect(shadowDomInjector.get(NeedsDirectiveFromAncestor).dependency).toBeAnInstanceOf(SimpleDirective); + }); + + it("should remove the dynamically-loaded component when dehydrating", () => { + var inj = injector(extraBindings); + inj.dynamicallyCreateComponent( + DirectiveBinding.createFromType( + DirectiveWithDestroy, + new DummyDirective({lifecycle: [onDestroy]}) + ), + appInjector); + var dir = inj.getDynamicallyLoadedComponent(); + + inj.dehydrate(); + + expect(inj.getDynamicallyLoadedComponent()).toBe(null); + expect(dir.onDestroyCounter).toBe(1); + + inj.hydrate(null, null, null); + + expect(inj.getDynamicallyLoadedComponent()).toBe(null); + }); + + it("should inject services of the dynamically-loaded component", () => { + var inj = injector(extraBindings); + var appInjector = Injector.resolveAndCreate([bind("service").toValue("Service")]); + inj.dynamicallyCreateComponent(DirectiveBinding.createFromType(NeedsService, null), appInjector); + expect(inj.getDynamicallyLoadedComponent().service).toEqual("Service"); + }); + }); + + describe('static attributes', () => { + it('should be injectable', () => { + var attributes = MapWrapper.create(); + MapWrapper.set(attributes, 'type', 'text'); + MapWrapper.set(attributes, 'title', ''); + + var inj = injector( + ListWrapper.concat([NeedsAttribute], extraBindings), null, false, null, attributes); + var needsAttribute = inj.get(NeedsAttribute); + + expect(needsAttribute.typeAttribute).toEqual('text'); + expect(needsAttribute.titleAttribute).toEqual(''); + expect(needsAttribute.fooAttribute).toEqual(null); + }); + + it('should be injectable without type annotation', () => { + var attributes = MapWrapper.create(); + MapWrapper.set(attributes, 'foo', 'bar'); + + var inj = injector( + ListWrapper.concat([NeedsAttributeNoType], extraBindings), null, false, null, attributes); + var needsAttribute = inj.get(NeedsAttributeNoType); + + expect(needsAttribute.fooAttribute).toEqual('bar'); + }); + }); + + describe("refs", () => { + it("should inject ElementRef", () => { + var inj = injector(ListWrapper.concat([NeedsElementRef], extraBindings)); + expect(inj.get(NeedsElementRef).elementRef).toBeAnInstanceOf(ElementRef); + }); + + it('should inject ChangeDetectorRef', () => { + var cd = new DynamicChangeDetector(null, null, null, [], []); + var view = new DummyView(); + var childView = new DummyView(); + childView.changeDetector = cd; + view.componentChildViews = [childView]; + var inj = injector(ListWrapper.concat([NeedsChangeDetectorRef], extraBindings), + null, false, new PreBuiltObjects(null, view, null)); + + expect(inj.get(NeedsChangeDetectorRef).changeDetectorRef).toBe(cd.ref); + }); + + it('should inject ViewContainerRef', () => { + var inj = injector(ListWrapper.concat([NeedsViewContainer], extraBindings)); + expect(inj.get(NeedsViewContainer).viewContainer).toBeAnInstanceOf(ViewContainerRef); + }); + + it("should inject ProtoViewRef", () => { + var protoView = new AppProtoView(null, null, null); + var inj = injector(ListWrapper.concat([NeedsProtoViewRef], extraBindings), + null, false, new PreBuiltObjects(null, null, protoView)); + + expect(inj.get(NeedsProtoViewRef).protoViewRef).toEqual(new ProtoViewRef(protoView)); + }); + + it("should throw if there is no ProtoViewRef", () => { + expect( + () => injector(ListWrapper.concat([NeedsProtoViewRef], extraBindings)) + ).toThrowError('No provider for ProtoViewRef! (NeedsProtoViewRef -> ProtoViewRef)'); + }); + + it('should inject null if there is no ProtoViewRef when the dependency is optional', () => { + var inj = injector( + ListWrapper.concat([OptionallyInjectsProtoViewRef], extraBindings)); + var instance = inj.get(OptionallyInjectsProtoViewRef); + expect(instance.protoViewRef).toBeNull(); + }); + }); + + describe('directive queries', () => { + var preBuildObjects = defaultPreBuiltObjects; + beforeEach(() => { + _constructionCount = 0; + }); + + function expectDirectives(query, type, expectedIndex) { + var currentCount = 0; + iterateListLike(query, (i) => { + expect(i).toBeAnInstanceOf(type); + expect(i.count).toBe(expectedIndex[currentCount]); + currentCount += 1; + }); + } + + it('should be injectable', () => { + var inj = injector(ListWrapper.concat([NeedsQuery], extraBindings), + null, false, preBuildObjects); + expect(inj.get(NeedsQuery).query).toBeAnInstanceOf(QueryList); + }); + + it('should contain directives on the same injector', () => { + var inj = injector(ListWrapper.concat([NeedsQuery, CountingDirective], extraBindings), + null, false, preBuildObjects); + + expectDirectives(inj.get(NeedsQuery).query, CountingDirective, [0]); + }); + + // Dart's restriction on static types in (a is A) makes this feature hard to implement. + // Current proposal is to add second parameter the Query constructor to take a + // comparison function to support user-defined definition of matching. + + //it('should support super class directives', () => { + // var inj = injector([NeedsQuery, FancyCountingDirective], null, null, preBuildObjects); + // + // expectDirectives(inj.get(NeedsQuery).query, FancyCountingDirective, [0]); + //}); + + it('should contain directives on the same and a child injector in construction order', () => { + var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]); + var protoChild = createPei( + protoParent, 1, ListWrapper.concat([CountingDirective], extraBindings)); + + var parent = protoParent.instantiate(null); + var child = protoChild.instantiate(parent); + parent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + child.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + + expectDirectives(parent.get(NeedsQuery).query, CountingDirective, [0,1]); + }); + + it('should reflect unlinking an injector', () => { + var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]); + var protoChild = createPei(protoParent, 1, + ListWrapper.concat([CountingDirective], extraBindings)); + + var parent = protoParent.instantiate(null); + var child = protoChild.instantiate(parent); + parent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + child.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + + child.unlink(); + + expectDirectives(parent.get(NeedsQuery).query, CountingDirective, [0]); + }); + + it('should reflect moving an injector as a last child', () => { + var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]); + var protoChild1 = createPei(protoParent, 1, [CountingDirective]); + var protoChild2 = createPei(protoParent, 1, + ListWrapper.concat([CountingDirective], extraBindings)); + + var parent = protoParent.instantiate(null); + var child1 = protoChild1.instantiate(parent); + var child2 = protoChild2.instantiate(parent); + + parent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + child1.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + child2.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + + child1.unlink(); + child1.link(parent); + + var queryList = parent.get(NeedsQuery).query; + expectDirectives(queryList, CountingDirective, [0, 2, 1]); + }); + + it('should reflect moving an injector as a first child', () => { + var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]); + var protoChild1 = createPei(protoParent, 1, [CountingDirective]); + var protoChild2 = createPei(protoParent, 1, + ListWrapper.concat([CountingDirective], extraBindings)); + + var parent = protoParent.instantiate(null); + var child1 = protoChild1.instantiate(parent); + var child2 = protoChild2.instantiate(parent); + + parent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + child1.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + child2.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + + child2.unlink(); + child2.linkAfter(parent, null); + + var queryList = parent.get(NeedsQuery).query; + expectDirectives(queryList, CountingDirective, [0, 2, 1]); + }); + + it('should support two concurrent queries for the same directive', () => { + var protoGrandParent = createPei(null, 0, [NeedsQuery]); + var protoParent = createPei(null, 0, [NeedsQuery]); + var protoChild = createPei(protoParent, 1, + ListWrapper.concat([CountingDirective], extraBindings)); + + var grandParent = protoGrandParent.instantiate(null); + var parent = protoParent.instantiate(grandParent); + var child = protoChild.instantiate(parent); + + grandParent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + parent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + child.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + + var queryList1 = grandParent.get(NeedsQuery).query; + var queryList2 = parent.get(NeedsQuery).query; + + expectDirectives(queryList1, CountingDirective, [0]); + expectDirectives(queryList2, CountingDirective, [0]); + + child.unlink(); + expectDirectives(queryList1, CountingDirective, []); + expectDirectives(queryList2, CountingDirective, []); }); - var componentDirective = DirectiveBinding.createFromType(SimpleDirective, directiveAnnotation); - expect(() => { - injector([componentDirective, NeedsService], null); - }).toThrowError('No provider for service! (NeedsService -> service)'); }); }); }); - - describe("lifecycle", () => { - it("should call onDestroy on directives subscribed to this event", function() { - var inj = injector([DirectiveBinding.createFromType(DirectiveWithDestroy, new DummyDirective({lifecycle: [onDestroy]}))]); - var destroy = inj.get(DirectiveWithDestroy); - inj.dehydrate(); - expect(destroy.onDestroyCounter).toBe(1); - }); - it("should work with services", function() { - var inj = injector([DirectiveBinding.createFromType(SimpleDirective, new DummyDirective({hostInjector: [SimpleService]}))]); - inj.dehydrate(); - }); - }); - - describe("dynamicallyCreateComponent", () => { - it("should create a component dynamically", () => { - var inj = injector([]); - - inj.dynamicallyCreateComponent(DirectiveBinding.createFromType(SimpleDirective, null), appInjector); - expect(inj.getDynamicallyLoadedComponent()).toBeAnInstanceOf(SimpleDirective); - expect(inj.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective); - }); - - it("should inject parent dependencies into the dynamically-loaded component", () => { - var inj = parentChildInjectors([SimpleDirective], []); - inj.dynamicallyCreateComponent(DirectiveBinding.createFromType(NeedsDirectiveFromAncestor, null), appInjector); - expect(inj.getDynamicallyLoadedComponent()).toBeAnInstanceOf(NeedsDirectiveFromAncestor); - expect(inj.getDynamicallyLoadedComponent().dependency).toBeAnInstanceOf(SimpleDirective); - }); - - it("should not inject the proxy component into the children of the dynamically-loaded component", () => { - var injWithDynamicallyLoadedComponent = injector([SimpleDirective]); - injWithDynamicallyLoadedComponent.dynamicallyCreateComponent(DirectiveBinding.createFromType(SomeOtherDirective, null), appInjector); - - var shadowDomProtoInjector = createPei(null, 0, [NeedsDirectiveFromAncestor]); - var shadowDomInj = shadowDomProtoInjector.instantiate(null); - - expect(() => - shadowDomInj.hydrate(appInjector, injWithDynamicallyLoadedComponent, defaultPreBuiltObjects)). - toThrowError(new RegExp("No provider for SimpleDirective")); - }); - - it("should not inject the dynamically-loaded component into directives on the same element", () => { - var dynamicComp = DirectiveBinding.createFromType(SomeOtherDirective, new Component()); - var proto = createPei(null, 0, [dynamicComp, NeedsDirective], 1, true); - var inj = proto.instantiate(null); - inj.dynamicallyCreateComponent( - DirectiveBinding.createFromType(SimpleDirective, null), appInjector); - - var error = null; - try { - inj.hydrate(Injector.resolveAndCreate([]), null, null); - } catch(e) { - error = e; - } - - expect(error.message).toEqual("No provider for SimpleDirective! (NeedsDirective -> SimpleDirective)"); - }); - - it("should inject the dynamically-loaded component into the children of the dynamically-loaded component", () => { - var componentDirective = DirectiveBinding.createFromType(SimpleDirective, null); - var injWithDynamicallyLoadedComponent = injector([]); - injWithDynamicallyLoadedComponent.dynamicallyCreateComponent(componentDirective, appInjector); - - var shadowDomProtoInjector = createPei(null, 0, [NeedsDirectiveFromAncestor]); - var shadowDomInjector = shadowDomProtoInjector.instantiate(null); - shadowDomInjector.hydrate(appInjector, injWithDynamicallyLoadedComponent, defaultPreBuiltObjects); - - expect(shadowDomInjector.get(NeedsDirectiveFromAncestor)).toBeAnInstanceOf(NeedsDirectiveFromAncestor); - expect(shadowDomInjector.get(NeedsDirectiveFromAncestor).dependency).toBeAnInstanceOf(SimpleDirective); - }); - - it("should remove the dynamically-loaded component when dehydrating", () => { - var inj = injector([]); - inj.dynamicallyCreateComponent( - DirectiveBinding.createFromType( - DirectiveWithDestroy, - new DummyDirective({lifecycle: [onDestroy]}) - ), - appInjector); - var dir = inj.getDynamicallyLoadedComponent(); - - inj.dehydrate(); - - expect(inj.getDynamicallyLoadedComponent()).toBe(null); - expect(dir.onDestroyCounter).toBe(1); - - inj.hydrate(null, null, null); - - expect(inj.getDynamicallyLoadedComponent()).toBe(null); - }); - - it("should inject services of the dynamically-loaded component", () => { - var inj = injector([]); - var appInjector = Injector.resolveAndCreate([bind("service").toValue("Service")]); - inj.dynamicallyCreateComponent(DirectiveBinding.createFromType(NeedsService, null), appInjector); - expect(inj.getDynamicallyLoadedComponent().service).toEqual("Service"); - }); - }); - - describe('static attributes', () => { - it('should be injectable', () => { - var attributes = MapWrapper.create(); - MapWrapper.set(attributes, 'type', 'text'); - MapWrapper.set(attributes, 'title', ''); - - var inj = injector([NeedsAttribute], null, false, null, attributes); - var needsAttribute = inj.get(NeedsAttribute); - - expect(needsAttribute.typeAttribute).toEqual('text'); - expect(needsAttribute.titleAttribute).toEqual(''); - expect(needsAttribute.fooAttribute).toEqual(null); - }); - - it('should be injectable without type annotation', () => { - var attributes = MapWrapper.create(); - MapWrapper.set(attributes, 'foo', 'bar'); - - var inj = injector([NeedsAttributeNoType], null, false, null, attributes); - var needsAttribute = inj.get(NeedsAttributeNoType); - - expect(needsAttribute.fooAttribute).toEqual('bar'); - }); - }); - - describe("refs", () => { - it("should inject ElementRef", () => { - var inj = injector([NeedsElementRef]); - expect(inj.get(NeedsElementRef).elementRef).toBeAnInstanceOf(ElementRef); - }); - - it('should inject ChangeDetectorRef', function () { - var cd = new DynamicChangeDetector(null, null, null, [], []); - var view = new DummyView(); - var childView = new DummyView(); - childView.changeDetector = cd; - view.componentChildViews = [childView]; - var inj = injector([NeedsChangeDetectorRef], null, false, new PreBuiltObjects(null, view, null)); - - expect(inj.get(NeedsChangeDetectorRef).changeDetectorRef).toBe(cd.ref); - }); - - it('should inject ViewContainerRef', () => { - var inj = injector([NeedsViewContainer]); - expect(inj.get(NeedsViewContainer).viewContainer).toBeAnInstanceOf(ViewContainerRef); - }); - - it("should inject ProtoViewRef", function () { - var protoView = new AppProtoView(null, null, null); - var inj = injector([NeedsProtoViewRef], null, false, new PreBuiltObjects(null, null, protoView)); - - expect(inj.get(NeedsProtoViewRef).protoViewRef).toEqual(new ProtoViewRef(protoView)); - }); - - it("should throw if there is no ProtoViewRef", function () { - expect( - () => injector([NeedsProtoViewRef]) - ).toThrowError('No provider for ProtoViewRef! (NeedsProtoViewRef -> ProtoViewRef)'); - }); - - it('should inject null if there is no ProtoViewRef when the dependency is optional', () => { - var inj = injector([OptionallyInjectsProtoViewRef]); - var instance = inj.get(OptionallyInjectsProtoViewRef); - expect(instance.protoViewRef).toBeNull(); - }); - }); - - describe('directive queries', () => { - var preBuildObjects = defaultPreBuiltObjects; - beforeEach(() => { - _constructionCount = 0; - }); - - function expectDirectives(query, type, expectedIndex) { - var currentCount = 0; - iterateListLike(query, (i) => { - expect(i).toBeAnInstanceOf(type); - expect(i.count).toBe(expectedIndex[currentCount]); - currentCount += 1; - }); - } - - it('should be injectable', () => { - var inj = injector([NeedsQuery], null, false, preBuildObjects); - expect(inj.get(NeedsQuery).query).toBeAnInstanceOf(QueryList); - }); - - it('should contain directives on the same injector', () => { - var inj = injector([NeedsQuery, CountingDirective], null, false, preBuildObjects); - - expectDirectives(inj.get(NeedsQuery).query, CountingDirective, [0]); - }); - - // Dart's restriction on static types in (a is A) makes this feature hard to implement. - // Current proposal is to add second parameter the Query constructor to take a - // comparison function to support user-defined definition of matching. - - //it('should support super class directives', () => { - // var inj = injector([NeedsQuery, FancyCountingDirective], null, null, preBuildObjects); - // - // expectDirectives(inj.get(NeedsQuery).query, FancyCountingDirective, [0]); - //}); - - it('should contain directives on the same and a child injector in construction order', () => { - var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]); - var protoChild = createPei(protoParent, 1, [CountingDirective]); - - var parent = protoParent.instantiate(null); - var child = protoChild.instantiate(parent); - parent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); - child.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); - - expectDirectives(parent.get(NeedsQuery).query, CountingDirective, [0,1]); - }); - - it('should reflect unlinking an injector', () => { - var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]); - var protoChild = createPei(protoParent, 1, [CountingDirective]); - - var parent = protoParent.instantiate(null); - var child = protoChild.instantiate(parent); - parent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); - child.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); - - child.unlink(); - - expectDirectives(parent.get(NeedsQuery).query, CountingDirective, [0]); - }); - - it('should reflect moving an injector as a last child', () => { - var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]); - var protoChild1 = createPei(protoParent, 1, [CountingDirective]); - var protoChild2 = createPei(protoParent, 1, [CountingDirective]); - - var parent = protoParent.instantiate(null); - var child1 = protoChild1.instantiate(parent); - var child2 = protoChild2.instantiate(parent); - - parent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); - child1.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); - child2.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); - - child1.unlink(); - child1.link(parent); - - var queryList = parent.get(NeedsQuery).query; - expectDirectives(queryList, CountingDirective, [0, 2, 1]); - }); - - it('should reflect moving an injector as a first child', () => { - var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]); - var protoChild1 = createPei(protoParent, 1, [CountingDirective]); - var protoChild2 = createPei(protoParent, 1, [CountingDirective]); - - var parent = protoParent.instantiate(null); - var child1 = protoChild1.instantiate(parent); - var child2 = protoChild2.instantiate(parent); - - parent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); - child1.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); - child2.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); - - child2.unlink(); - child2.linkAfter(parent, null); - - var queryList = parent.get(NeedsQuery).query; - expectDirectives(queryList, CountingDirective, [0, 2, 1]); - }); - - it('should support two concurrent queries for the same directive', () => { - var protoGrandParent = createPei(null, 0, [NeedsQuery]); - var protoParent = createPei(null, 0, [NeedsQuery]); - var protoChild = createPei(protoParent, 1, [CountingDirective]); - - var grandParent = protoGrandParent.instantiate(null); - var parent = protoParent.instantiate(grandParent); - var child = protoChild.instantiate(parent); - - grandParent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); - parent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); - child.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); - - var queryList1 = grandParent.get(NeedsQuery).query; - var queryList2 = parent.get(NeedsQuery).query; - - expectDirectives(queryList1, CountingDirective, [0]); - expectDirectives(queryList2, CountingDirective, [0]); - - child.unlink(); - expectDirectives(queryList1, CountingDirective, []); - expectDirectives(queryList2, CountingDirective, []); - }); - }); }); }