From b066b8d15aa5dbeb4c096c5595d0eb69ab5525a6 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Sat, 16 May 2015 11:01:02 -0700 Subject: [PATCH] feat(di): added hostInjector and viewInjector to the Directive annotation --- modules/angular2/di.ts | 2 +- .../src/core/annotations_impl/annotations.js | 82 +++ .../src/core/annotations_impl/visibility.js | 111 +++- .../src/core/compiler/element_injector.js | 520 ++++++++++-------- .../src/core/compiler/proto_view_factory.js | 2 +- .../src/core/compiler/view_manager_utils.js | 4 +- modules/angular2/src/di/injector.ts | 13 +- .../core/compiler/element_injector_spec.js | 424 +++++++------- .../test/core/compiler/integration_spec.js | 118 +++- .../core/compiler/view_manager_utils_spec.js | 8 +- .../core/forward_ref_integration_spec.es6 | 2 +- .../e2e_test/element_injector_perf.es6 | 6 +- .../element_injector_benchmark.html | 2 +- .../element_injector_benchmark.js | 22 +- 14 files changed, 877 insertions(+), 439 deletions(-) diff --git a/modules/angular2/di.ts b/modules/angular2/di.ts index 045cce6627..aedb78c01e 100644 --- a/modules/angular2/di.ts +++ b/modules/angular2/di.ts @@ -8,7 +8,7 @@ export * from './src/di/annotations'; export * from './src/di/decorators'; export * from './src/di/forward_ref'; -export {Injector} from './src/di/injector'; +export {resolveBindings, Injector} from './src/di/injector'; export {Binding, ResolvedBinding, Dependency, bind} from './src/di/binding'; export {Key, KeyRegistry, TypeLiteral} from './src/di/key'; export { diff --git a/modules/angular2/src/core/annotations_impl/annotations.js b/modules/angular2/src/core/annotations_impl/annotations.js index 6f6636c251..e1b4c48da0 100644 --- a/modules/angular2/src/core/annotations_impl/annotations.js +++ b/modules/angular2/src/core/annotations_impl/annotations.js @@ -660,6 +660,37 @@ export class Directive extends Injectable { //TODO(vsavkin): This would better fall under the Macro directive concept. compileChildren: boolean; + /** + * Defines the set of injectable objects that are visible to a Directive and its light dom children. + * + * ## Simple Example + * + * Here is an example of a class that can be injected: + * + * ``` + * class Greeter { + * greet(name:string) { + * return 'Hello ' + name + '!'; + * } + * } + * + * @Directive({ + * selector: 'greet', + * hostInjector: [ + * Greeter + * ] + * }) + * class HelloWorld { + * greeter:Greeter; + * + * constructor(greeter:Greeter) { + * this.greeter = greeter; + * } + * } + * ``` + */ + hostInjector:List; + @CONST() constructor({ selector, @@ -670,6 +701,7 @@ export class Directive extends Injectable { hostAttributes, hostActions, lifecycle, + hostInjector, compileChildren = true, }:{ selector:string, @@ -680,6 +712,7 @@ export class Directive extends Injectable { hostAttributes: any, hostActions: any, lifecycle:List, + hostInjector:List, compileChildren:boolean }={}) { @@ -693,6 +726,7 @@ export class Directive extends Injectable { this.hostActions = hostActions; this.lifecycle = lifecycle; this.compileChildren = compileChildren; + this.hostInjector = hostInjector; } /** @@ -845,6 +879,48 @@ export class Component extends Directive { */ appInjector:List; + /** + * Defines the set of injectable objects that are visible to its view dom children. + * + * ## Simple Example + * + * Here is an example of a class that can be injected: + * + * ``` + * class Greeter { + * greet(name:string) { + * return 'Hello ' + name + '!'; + * } + * } + * + * @Directive({ + * selector: 'needs-greeter' + * }) + * class NeedsGreeter { + * greeter:Greeter; + * + * constructor(greeter:Greeter) { + * this.greeter = greeter; + * } + * } + * + * @Component({ + * selector: 'greet', + * viewInjector: [ + * Greeter + * ] + * }) + * @View({ + * template: ``, + * directives: [NeedsGreeter] + * }) + * class HelloWorld { + * } + * + * ``` + */ + viewInjector:List; + @CONST() constructor({ selector, @@ -856,6 +932,8 @@ export class Component extends Directive { hostActions, appInjector, lifecycle, + hostInjector, + viewInjector, changeDetection = DEFAULT, compileChildren = true }:{ @@ -868,6 +946,8 @@ export class Component extends Directive { hostActions:any, appInjector:List, lifecycle:List, + hostInjector:List, + viewInjector:List, changeDetection:string, compileChildren:boolean }={}) @@ -880,12 +960,14 @@ export class Component extends Directive { hostProperties: hostProperties, hostAttributes: hostAttributes, hostActions: hostActions, + hostInjector: hostInjector, lifecycle: lifecycle, compileChildren: compileChildren }); this.changeDetection = changeDetection; this.appInjector = appInjector; + this.viewInjector = viewInjector; } } diff --git a/modules/angular2/src/core/annotations_impl/visibility.js b/modules/angular2/src/core/annotations_impl/visibility.js index 10f47a5233..c878d5165c 100644 --- a/modules/angular2/src/core/annotations_impl/visibility.js +++ b/modules/angular2/src/core/annotations_impl/visibility.js @@ -1,6 +1,66 @@ import {CONST} from 'angular2/src/facade/lang'; import {DependencyAnnotation} from 'angular2/src/di/annotations_impl'; +export class Visibility extends DependencyAnnotation { + depth: number; + crossComponentBoundaries: boolean; + + @CONST() + constructor(depth:number, crossComponentBoundaries:boolean) { + super(); + this.depth = depth; + this.crossComponentBoundaries = crossComponentBoundaries; + } + + shouldIncludeSelf():boolean { + return this.depth === 0; + } +} + +/** + * Specifies that an injector should retrieve a dependency from its element. + * + * ## Example + * + * Here is a simple directive that retrieves a dependency from its element. + * + * ``` + * @Directive({ + * selector: '[dependency]', + * properties: { + * 'id':'dependency' + * } + * }) + * class Dependency { + * id:string; + * } + * + * + * @Directive({ + * selector: '[my-directive]' + * }) + * class Dependency { + * constructor(@Self() dependency:Dependency) { + * expect(dependency.id).toEqual(1); + * }; + * } + * ``` + * + * We use this with the following HTML template: + * + * ``` + *
+ * ``` + * + * @exportedAs angular2/annotations + */ +export class Self extends Visibility { + @CONST() + constructor() { + super(0, false); + } +} + /** * Specifies that an injector should retrieve a dependency from the direct parent. * @@ -42,15 +102,15 @@ import {DependencyAnnotation} from 'angular2/src/di/annotations_impl'; * * @exportedAs angular2/annotations */ -export class Parent extends DependencyAnnotation { +export class Parent extends Visibility { @CONST() constructor() { - super(); + super(1, false); } } /** - * Specifies that an injector should retrieve a dependency from any ancestor element. + * Specifies that an injector should retrieve a dependency from any ancestor element within the same shadow boundary. * * An ancestor is any element between the parent element and shadow root. * @@ -103,9 +163,50 @@ export class Parent extends DependencyAnnotation { * * @exportedAs angular2/annotations */ -export class Ancestor extends DependencyAnnotation { +export class Ancestor extends Visibility { @CONST() constructor() { - super(); + super(999999, false); + } +} + +/** + * Specifies that an injector should retrieve a dependency from any ancestor element. + * + * An ancestor is any element between the parent element and shadow root. + * + * + * ## Example + * + * Here is a simple directive that retrieves a dependency from an ancestor element. + * + * ``` + * @Directive({ + * selector: '[dependency]', + * properties: { + * 'id':'dependency' + * } + * }) + * class Dependency { + * id:string; + * } + * + * + * @Directive({ + * selector: '[my-directive]' + * }) + * class Dependency { + * constructor(@Unbounded() dependency:Dependency) { + * expect(dependency.id).toEqual(2); + * }; + * } + * ``` + * + * @exportedAs angular2/annotations + */ +export class Unbounded extends Visibility { + @CONST() + constructor() { + super(999999, true); } } diff --git a/modules/angular2/src/core/compiler/element_injector.js b/modules/angular2/src/core/compiler/element_injector.js index 0ceb06101c..2658f0b1ff 100644 --- a/modules/angular2/src/core/compiler/element_injector.js +++ b/modules/angular2/src/core/compiler/element_injector.js @@ -1,10 +1,9 @@ import {isPresent, isBlank, Type, int, BaseException, stringify} from 'angular2/src/facade/lang'; import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; -import {Math} from 'angular2/src/facade/math'; import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {Injector, Key, Dependency, bind, Binding, ResolvedBinding, NoBindingError, - AbstractBindingError, CyclicDependencyError, resolveForwardRef} from 'angular2/di'; -import {Parent, Ancestor} from 'angular2/src/core/annotations_impl/visibility'; + AbstractBindingError, CyclicDependencyError, resolveForwardRef, resolveBindings} from 'angular2/di'; +import {Visibility, Self} from 'angular2/src/core/annotations_impl/visibility'; import {Attribute, Query} from 'angular2/src/core/annotations_impl/di'; import * as viewModule from './view'; import * as avmModule from './view_manager'; @@ -20,8 +19,6 @@ import {DirectiveMetadata} from 'angular2/src/render/api'; var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10; -var MAX_DEPTH = Math.pow(2, 30) - 1; - var _undefined = new Object(); var _staticKeys; @@ -177,14 +174,14 @@ export class TreeNode { } export class DirectiveDependency extends Dependency { - depth:int; + visibility:Visibility; attributeName:string; queryDirective; constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean, properties:List, - depth:int, attributeName:string, queryDirective) { + visibility:Visibility, attributeName:string, queryDirective) { super(key, asPromise, lazy, optional, properties); - this.depth = depth; + this.visibility = visibility;; this.attributeName = attributeName; this.queryDirective = queryDirective; this._verify(); @@ -199,18 +196,17 @@ export class DirectiveDependency extends Dependency { } static createFrom(d:Dependency):Dependency { - return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional, - d.properties, DirectiveDependency._depth(d.properties), + return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional, d.properties, + DirectiveDependency._visibility(d.properties), DirectiveDependency._attributeName(d.properties), DirectiveDependency._query(d.properties) ); } - static _depth(properties):int { - if (properties.length == 0) return 0; - if (ListWrapper.any(properties, p => p instanceof Parent)) return 1; - if (ListWrapper.any(properties, p => p instanceof Ancestor)) return MAX_DEPTH; - return 0; + static _visibility(properties):Visibility { + if (properties.length == 0) return new Self(); + var p = ListWrapper.find(properties, p => p instanceof Visibility); + return isPresent(p) ? p : new Self(); } static _attributeName(properties):string { @@ -225,13 +221,18 @@ export class DirectiveDependency extends Dependency { } export class DirectiveBinding extends ResolvedBinding { - resolvedInjectables:List; + resolvedAppInjectables:List; + resolvedHostInjectables:List; + resolvedViewInjectables:List; metadata: DirectiveMetadata; constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean, - resolvedInjectables:List, metadata:DirectiveMetadata, annotation: Directive) { + resolvedAppInjectables:List, resolvedHostInjectables:List, + resolvedViewInjectables:List, metadata:DirectiveMetadata) { super(key, factory, dependencies, providedAsPromise); - this.resolvedInjectables = resolvedInjectables; + this.resolvedAppInjectables = resolvedAppInjectables; + this.resolvedHostInjectables = resolvedHostInjectables; + this.resolvedViewInjectables = resolvedViewInjectables; this.metadata = metadata; } @@ -260,59 +261,52 @@ export class DirectiveBinding extends ResolvedBinding { } get changeDetection() { - if (isPresent(metadata)) { - return metadata.changeDetection; - } else { - return null; - } + return this.metadata.changeDetection; } - static createFromBinding(b:Binding, ann:Directive):DirectiveBinding { + static createFromBinding(binding:Binding, ann:Directive):DirectiveBinding { if (isBlank(ann)) { ann = new Directive(); } - var rb = b.resolve(); + + var rb = binding.resolve(); var deps = ListWrapper.map(rb.dependencies, DirectiveDependency.createFrom); - var renderType; - var compileChildren = ann.compileChildren; - var resolvedInjectables = null; - var changeDetection = null; - if (ann instanceof Component) { - renderType = DirectiveMetadata.COMPONENT_TYPE; - if (isPresent(ann.appInjector)) { - resolvedInjectables = Injector.resolve(ann.appInjector); - } - changeDetection = ann.changeDetection; - } else { - renderType = DirectiveMetadata.DIRECTIVE_TYPE; - } - var readAttributes = []; - ListWrapper.forEach(deps, (dep) => { - if (isPresent(dep.attributeName)) { - ListWrapper.push(readAttributes, dep.attributeName); - } - }); + var resolvedAppInjectables = ann instanceof Component && isPresent(ann.appInjector) ? Injector.resolve(ann.appInjector) : []; + var resolvedHostInjectables = isPresent(ann.hostInjector) ? resolveBindings(ann.hostInjector) : []; + var resolvedViewInjectables = ann instanceof Component && isPresent(ann.viewInjector) ? resolveBindings(ann.viewInjector) : []; + var metadata = new DirectiveMetadata({ id: stringify(rb.key.token), - type: renderType, + type: ann instanceof Component ? DirectiveMetadata.COMPONENT_TYPE : DirectiveMetadata.DIRECTIVE_TYPE, selector: ann.selector, - compileChildren: compileChildren, + compileChildren: ann.compileChildren, events: ann.events, hostListeners: isPresent(ann.hostListeners) ? MapWrapper.createFromStringMap(ann.hostListeners) : null, hostProperties: isPresent(ann.hostProperties) ? MapWrapper.createFromStringMap(ann.hostProperties) : null, hostAttributes: isPresent(ann.hostAttributes) ? MapWrapper.createFromStringMap(ann.hostAttributes) : null, hostActions: isPresent(ann.hostActions) ? MapWrapper.createFromStringMap(ann.hostActions) : null, properties: isPresent(ann.properties) ? MapWrapper.createFromStringMap(ann.properties) : null, - readAttributes: readAttributes, + readAttributes: DirectiveBinding._readAttributes(deps), callOnDestroy: ann.hasLifecycleHook(onDestroy), callOnChange: ann.hasLifecycleHook(onChange), callOnAllChangesDone: ann.hasLifecycleHook(onAllChangesDone), - changeDetection: changeDetection + changeDetection: ann instanceof Component ? ann.changeDetection : null }); - return new DirectiveBinding(rb.key, rb.factory, deps, rb.providedAsPromise, resolvedInjectables, metadata, ann); + return new DirectiveBinding(rb.key, rb.factory, deps, rb.providedAsPromise, resolvedAppInjectables, + resolvedHostInjectables, resolvedViewInjectables, metadata); } - static createFromType(type:Type, annotation:Directive):DirectiveBinding { + static _readAttributes(deps) { + var readAttributes = []; + ListWrapper.forEach(deps, (dep) => { + if (isPresent(dep.attributeName)) { + ListWrapper.push(readAttributes, dep.attributeName); + } + }); + return readAttributes; + } + + static createFromType(type:Type, annotation:Directive):DirectiveBinding { var binding = new Binding(type, {toClass: type}); return DirectiveBinding.createFromBinding(binding, annotation); } @@ -363,6 +357,42 @@ class HostActionAccessor { } } +const LIGHT_DOM = 1; +const SHADOW_DOM = 2; +const LIGHT_DOM_AND_SHADOW_DOM = 3; + +class BindingData { + binding:ResolvedBinding; + visibility:number; + + constructor(binding:ResolvedBinding, visibility:number) { + this.binding = binding; + this.visibility = visibility; + } + + getKeyId() { + return this.binding.key.id; + } + + createEventEmitterAccessors() { + if (!(this.binding instanceof DirectiveBinding)) return []; + var db:DirectiveBinding = this.binding; + return ListWrapper.map(db.eventEmitters, eventName => + new EventEmitterAccessor(eventName, reflector.getter(eventName)) + ); + } + + createHostActionAccessors() { + if (!(this.binding instanceof DirectiveBinding)) return []; + var res = []; + var db:DirectiveBinding = this.binding; + MapWrapper.forEach(db.hostActions, (actionExpression, actionName) => { + ListWrapper.push(res, new HostActionAccessor(actionExpression, reflector.getter(actionName))) + }); + return res; + } +} + /** Difference between di.Injector and ElementInjector @@ -382,20 +412,19 @@ ElementInjector: PERF BENCHMARK: http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/ */ - - export class ProtoElementInjector { - _binding0:DirectiveBinding; - _binding1:DirectiveBinding; - _binding2:DirectiveBinding; - _binding3:DirectiveBinding; - _binding4:DirectiveBinding; - _binding5:DirectiveBinding; - _binding6:DirectiveBinding; - _binding7:DirectiveBinding; - _binding8:DirectiveBinding; - _binding9:DirectiveBinding; - _binding0IsComponent:boolean; + // 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; @@ -406,6 +435,18 @@ export class ProtoElementInjector { _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; @@ -414,8 +455,6 @@ export class ProtoElementInjector { eventEmitterAccessors:List>; hostActionAccessors:List>; - numberOfDirectives:number; - /** Whether the element is exported as $implicit. */ exportElement:boolean; @@ -425,109 +464,144 @@ export class ProtoElementInjector { /** The variable name that will be set to $implicit for the element. */ exportImplicitName:string; - constructor(parent:ProtoElementInjector, index:int, bindings:List, firstBindingIsComponent:boolean = false, distanceToParent:number = 0) { + _firstBindingIsComponent:boolean; + + static create(parent:ProtoElementInjector, index:int, bindings:List, firstBindingIsComponent:boolean, distanceToParent:number) { + var bd = []; + + ProtoElementInjector._createDirectiveBindingData(bindings, bd, firstBindingIsComponent); + ProtoElementInjector._createHostInjectorBindingData(bindings, bd); + if (firstBindingIsComponent) { + ProtoElementInjector._createViewInjectorBindingData(bindings, bd); + } + + return new ProtoElementInjector(parent, index, bd, distanceToParent, firstBindingIsComponent); + } + + static _createDirectiveBindingData(bindings:List, bd:List, firstBindingIsComponent:boolean) { + if (firstBindingIsComponent) { + ListWrapper.push(bd, new BindingData(bindings[0], LIGHT_DOM_AND_SHADOW_DOM)); + for (var i = 1; i < bindings.length; ++i) { + ListWrapper.push(bd, new BindingData(bindings[i], LIGHT_DOM)); + } + } else { + ListWrapper.forEach(bindings, b => { + ListWrapper.push(bd, new BindingData(b, LIGHT_DOM)) + }); + } + } + + static _createHostInjectorBindingData(bindings:List, bd:List) { + ListWrapper.forEach(bindings, b => { + ListWrapper.forEach(b.resolvedHostInjectables, b => { + ListWrapper.push(bd, new BindingData(b, LIGHT_DOM)); + }); + }); + } + + static _createViewInjectorBindingData(bindings:List, bd:List) { + ListWrapper.forEach(bindings[0].resolvedViewInjectables, + b => ListWrapper.push(bd, new BindingData(b, SHADOW_DOM))); + } + + constructor(parent:ProtoElementInjector, index:int, bd:List, distanceToParent:number, firstBindingIsComponent:boolean) { this.parent = parent; this.index = index; this.distanceToParent = distanceToParent; this.exportComponent = false; this.exportElement = false; - - this._binding0IsComponent = firstBindingIsComponent; - this._binding0 = null; this._keyId0 = null; - this._binding1 = null; this._keyId1 = null; - this._binding2 = null; this._keyId2 = null; - this._binding3 = null; this._keyId3 = null; - this._binding4 = null; this._keyId4 = null; - this._binding5 = null; this._keyId5 = null; - this._binding6 = null; this._keyId6 = null; - this._binding7 = null; this._keyId7 = null; - this._binding8 = null; this._keyId8 = null; - this._binding9 = null; this._keyId9 = null; - - this.numberOfDirectives = bindings.length; - var length = bindings.length; + 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 = this._createBinding(bindings[0]); - this._keyId0 = this._binding0.key.id; - this.eventEmitterAccessors[0] = this._createEventEmitterAccessors(this._binding0); - this.hostActionAccessors[0] = this._createHostActionAccessors(this._binding0); + 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 = this._createBinding(bindings[1]); - this._keyId1 = this._binding1.key.id; - this.eventEmitterAccessors[1] = this._createEventEmitterAccessors(this._binding1); - this.hostActionAccessors[1] = this._createHostActionAccessors(this._binding1); + 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 = this._createBinding(bindings[2]); - this._keyId2 = this._binding2.key.id; - this.eventEmitterAccessors[2] = this._createEventEmitterAccessors(this._binding2); - this.hostActionAccessors[2] = this._createHostActionAccessors(this._binding2); + 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 = this._createBinding(bindings[3]); - this._keyId3 = this._binding3.key.id; - this.eventEmitterAccessors[3] = this._createEventEmitterAccessors(this._binding3); - this.hostActionAccessors[3] = this._createHostActionAccessors(this._binding3); + 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 = this._createBinding(bindings[4]); - this._keyId4 = this._binding4.key.id; - this.eventEmitterAccessors[4] = this._createEventEmitterAccessors(this._binding4); - this.hostActionAccessors[4] = this._createHostActionAccessors(this._binding4); + 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 = this._createBinding(bindings[5]); - this._keyId5 = this._binding5.key.id; - this.eventEmitterAccessors[5] = this._createEventEmitterAccessors(this._binding5); - this.hostActionAccessors[5] = this._createHostActionAccessors(this._binding5); + 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 = this._createBinding(bindings[6]); - this._keyId6 = this._binding6.key.id; - this.eventEmitterAccessors[6] = this._createEventEmitterAccessors(this._binding6); - this.hostActionAccessors[6] = this._createHostActionAccessors(this._binding6); + 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 = this._createBinding(bindings[7]); - this._keyId7 = this._binding7.key.id; - this.eventEmitterAccessors[7] = this._createEventEmitterAccessors(this._binding7); - this.hostActionAccessors[7] = this._createHostActionAccessors(this._binding7); + 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 = this._createBinding(bindings[8]); - this._keyId8 = this._binding8.key.id; - this.eventEmitterAccessors[8] = this._createEventEmitterAccessors(this._binding8); - this.hostActionAccessors[8] = this._createHostActionAccessors(this._binding8); + 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 = this._createBinding(bindings[9]); - this._keyId9 = this._binding9.key.id; - this.eventEmitterAccessors[9] = this._createEventEmitterAccessors(this._binding9); - this.hostActionAccessors[9] = this._createHostActionAccessors(this._binding9); + 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.'; } } - _createEventEmitterAccessors(b:DirectiveBinding) { - return ListWrapper.map(b.eventEmitters, eventName => - new EventEmitterAccessor(eventName, reflector.getter(eventName)) - ); - } - - _createHostActionAccessors(b:DirectiveBinding) { - var res = []; - MapWrapper.forEach(b.hostActions, (actionExpression, actionName) => { - ListWrapper.push(res, new HostActionAccessor(actionExpression, reflector.getter(actionName))) - }); - return res; - } - instantiate(parent:ElementInjector):ElementInjector { return new ElementInjector(this, parent); } @@ -536,20 +610,11 @@ export class ProtoElementInjector { return this.distanceToParent < 2 ? this.parent : null; } - _createBinding(bindingOrType) { - if (bindingOrType instanceof DirectiveBinding) { - return bindingOrType; - } else { - var b = bind(bindingOrType).toClass(bindingOrType); - return DirectiveBinding.createFromBinding(b, null); - } - } - get hasBindings():boolean { return isPresent(this._binding0); } - getDirectiveBindingAtIndex(index:int) { + getBindingAtIndex(index:int) { if (index == 0) return this._binding0; if (index == 1) return this._binding1; if (index == 2) return this._binding2; @@ -597,7 +662,7 @@ export class ElementInjector extends TreeNode { super(parent); this._proto = proto; - //we cannot call clearDirectives because fields won't be detected + //we cannot call dehydrate because fields won't be detected this._preBuiltObjects = null; this._lightDomAppInjector = null; this._shadowDomAppInjector = null; @@ -617,7 +682,7 @@ export class ElementInjector extends TreeNode { this._buildQueries(); } - clearDirectives() { + dehydrate() { this._host = null; this._preBuiltObjects = null; this._lightDomAppInjector = null; @@ -625,16 +690,16 @@ export class ElementInjector extends TreeNode { var p = this._proto; - if (isPresent(p._binding0) && p._binding0.callOnDestroy) {this._obj0.onDestroy();} - if (isPresent(p._binding1) && p._binding1.callOnDestroy) {this._obj1.onDestroy();} - if (isPresent(p._binding2) && p._binding2.callOnDestroy) {this._obj2.onDestroy();} - if (isPresent(p._binding3) && p._binding3.callOnDestroy) {this._obj3.onDestroy();} - if (isPresent(p._binding4) && p._binding4.callOnDestroy) {this._obj4.onDestroy();} - if (isPresent(p._binding5) && p._binding5.callOnDestroy) {this._obj5.onDestroy();} - if (isPresent(p._binding6) && p._binding6.callOnDestroy) {this._obj6.onDestroy();} - if (isPresent(p._binding7) && p._binding7.callOnDestroy) {this._obj7.onDestroy();} - if (isPresent(p._binding8) && p._binding8.callOnDestroy) {this._obj8.onDestroy();} - if (isPresent(p._binding9) && p._binding9.callOnDestroy) {this._obj9.onDestroy();} + 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(); } @@ -655,45 +720,38 @@ export class ElementInjector extends TreeNode { this._constructionCounter = 0; } - instantiateDirectives( - lightDomAppInjector:Injector, - host:ElementInjector, - preBuiltObjects:PreBuiltObjects) { - var shadowDomAppInjector = null; - if (this._proto._binding0IsComponent) { - shadowDomAppInjector = this._createShadowDomAppInjector(this._proto._binding0, lightDomAppInjector); - } - this._host = host; - this._checkShadowDomAppInjector(shadowDomAppInjector); - - this._preBuiltObjects = preBuiltObjects; - this._lightDomAppInjector = lightDomAppInjector; - this._shadowDomAppInjector = shadowDomAppInjector; + hydrate(injector:Injector, host:ElementInjector, preBuiltObjects:PreBuiltObjects) { var p = this._proto; - if (isPresent(p._keyId0)) this._getDirectiveByKeyId(p._keyId0); - if (isPresent(p._keyId1)) this._getDirectiveByKeyId(p._keyId1); - if (isPresent(p._keyId2)) this._getDirectiveByKeyId(p._keyId2); - if (isPresent(p._keyId3)) this._getDirectiveByKeyId(p._keyId3); - if (isPresent(p._keyId4)) this._getDirectiveByKeyId(p._keyId4); - if (isPresent(p._keyId5)) this._getDirectiveByKeyId(p._keyId5); - if (isPresent(p._keyId6)) this._getDirectiveByKeyId(p._keyId6); - if (isPresent(p._keyId7)) this._getDirectiveByKeyId(p._keyId7); - if (isPresent(p._keyId8)) this._getDirectiveByKeyId(p._keyId8); - if (isPresent(p._keyId9)) this._getDirectiveByKeyId(p._keyId9); + + this._host = host; + this._lightDomAppInjector = injector; + this._preBuiltObjects = preBuiltObjects; + + if (p._firstBindingIsComponent) { + this._shadowDomAppInjector = this._createShadowDomAppInjector(p._binding0, 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); } _createShadowDomAppInjector(componentDirective:DirectiveBinding, appInjector:Injector) { - var shadowDomAppInjector = null; - - // shadowDomAppInjector - var injectables = componentDirective.resolvedInjectables; - if (isPresent(injectables)) { - shadowDomAppInjector = appInjector.createChildFromResolved(injectables); + if (! ListWrapper.isEmpty(componentDirective.resolvedAppInjectables)) { + return appInjector.createChildFromResolved(componentDirective.resolvedAppInjectables); } else { - shadowDomAppInjector = appInjector; + return appInjector; } - return shadowDomAppInjector; } dynamicallyCreateComponent(componentDirective:DirectiveBinding, parentInjector:Injector) { @@ -704,9 +762,9 @@ export class ElementInjector extends TreeNode { } _checkShadowDomAppInjector(shadowDomAppInjector:Injector) { - if (this._proto._binding0IsComponent && isBlank(shadowDomAppInjector)) { + if (this._proto._firstBindingIsComponent && isBlank(shadowDomAppInjector)) { throw new BaseException('A shadowDomAppInjector is required as this ElementInjector contains a component'); - } else if (!this._proto._binding0IsComponent && isPresent(shadowDomAppInjector)) { + } else if (!this._proto._firstBindingIsComponent && isPresent(shadowDomAppInjector)) { throw new BaseException('No shadowDomAppInjector allowed as there is not component stored in this ElementInjector'); } } @@ -716,7 +774,7 @@ export class ElementInjector extends TreeNode { return this._dynamicallyCreatedComponent; } - return this._getByKey(Key.get(token), 0, false, null); + return this._getByKey(Key.get(token), new Self(), false, null); } _isDynamicallyLoadedComponent(token) { @@ -725,7 +783,7 @@ export class ElementInjector extends TreeNode { } hasDirective(type:Type):boolean { - return this._getDirectiveByKeyId(Key.get(type).id) !== _undefined; + return this._getObjByKeyId(Key.get(type).id, LIGHT_DOM_AND_SHADOW_DOM) !== _undefined; } getEventEmitterAccessors() { @@ -737,11 +795,7 @@ export class ElementInjector extends TreeNode { } getComponent() { - if (this._proto._binding0IsComponent) { - return this._obj0; - } else { - throw new BaseException('There is no component stored in this ElementInjector'); - } + return this._obj0; } getElementRef() { @@ -761,7 +815,7 @@ export class ElementInjector extends TreeNode { } _isComponentKey(key:Key) { - return this._proto._binding0IsComponent && key.id === this._proto._keyId0; + return this._proto._firstBindingIsComponent && key.id === this._proto._keyId0; } _isDynamicallyLoadedComponentKey(key:Key) { @@ -839,7 +893,7 @@ export class ElementInjector extends TreeNode { } return new ProtoViewRef(this._preBuiltObjects.protoView); } - return this._getByKey(dep.key, dep.depth, dep.optional, requestor); + return this._getByKey(dep.key, dep.visibility, dep.optional, requestor); } _buildAttribute(dep): string { @@ -965,25 +1019,46 @@ export class ElementInjector extends TreeNode { if (this._query2 == query) this._query2 = null; } - _getByKey(key:Key, depth:number, optional:boolean, requestor:Key) { + _getByKey(key:Key, visibility:Visibility, optional:boolean, requestor:Key) { var ei = this; - if (! this._shouldIncludeSelf(depth)) { - depth -= ei._proto.distanceToParent; - ei = ei._parent; - } + var currentVisibility = LIGHT_DOM; + var depth = visibility.depth; + + if (! visibility.shouldIncludeSelf()) { + depth -= ei._proto.distanceToParent; + + if (isPresent(ei._parent)) { + ei = ei._parent; + } else { + ei = ei._host; + if (!visibility.crossComponentBoundaries) { + currentVisibility = SHADOW_DOM; + } + } + } + while (ei != null && depth >= 0) { var preBuiltObj = ei._getPreBuiltObjectByKeyId(key.id); if (preBuiltObj !== _undefined) return preBuiltObj; - var dir = ei._getDirectiveByKeyId(key.id); + var dir = ei._getObjByKeyId(key.id, currentVisibility); if (dir !== _undefined) return dir; depth -= ei._proto.distanceToParent; - ei = ei._parent; + + if (currentVisibility === SHADOW_DOM) break; + + if (isPresent(ei._parent)) { + ei = ei._parent; + } else { + ei = ei._host; + if (!visibility.crossComponentBoundaries) { + currentVisibility = SHADOW_DOM; + } + } } - if (isPresent(this._host) && this._host._isComponentKey(key)) { return this._host.getComponent(); } else if (isPresent(this._host) && this._host._isDynamicallyLoadedComponentKey(key)) { @@ -994,7 +1069,7 @@ export class ElementInjector extends TreeNode { return this._appInjector(requestor).get(key); } } - + _appInjector(requestor:Key) { if (isPresent(requestor) && (this._isComponentKey(requestor) || this._isDynamicallyLoadedComponentKey(requestor))) { return this._shadowDomAppInjector; @@ -1003,10 +1078,6 @@ export class ElementInjector extends TreeNode { } } - _shouldIncludeSelf(depth:int) { - return depth === 0; - } - _getPreBuiltObjectByKeyId(keyId:int) { var staticKeys = StaticKeys.instance(); if (keyId === staticKeys.viewManagerId) return this._preBuiltObjects.viewManager; @@ -1015,19 +1086,20 @@ export class ElementInjector extends TreeNode { return _undefined; } - _getDirectiveByKeyId(keyId:int) { + _getObjByKeyId(keyId:int, visibility:number) { var p = this._proto; - if (p._keyId0 === keyId) {if (isBlank(this._obj0)){this._obj0 = this._new(p._binding0);} return this._obj0;} - if (p._keyId1 === keyId) {if (isBlank(this._obj1)){this._obj1 = this._new(p._binding1);} return this._obj1;} - if (p._keyId2 === keyId) {if (isBlank(this._obj2)){this._obj2 = this._new(p._binding2);} return this._obj2;} - if (p._keyId3 === keyId) {if (isBlank(this._obj3)){this._obj3 = this._new(p._binding3);} return this._obj3;} - if (p._keyId4 === keyId) {if (isBlank(this._obj4)){this._obj4 = this._new(p._binding4);} return this._obj4;} - if (p._keyId5 === keyId) {if (isBlank(this._obj5)){this._obj5 = this._new(p._binding5);} return this._obj5;} - if (p._keyId6 === keyId) {if (isBlank(this._obj6)){this._obj6 = this._new(p._binding6);} return this._obj6;} - if (p._keyId7 === keyId) {if (isBlank(this._obj7)){this._obj7 = this._new(p._binding7);} return this._obj7;} - if (p._keyId8 === keyId) {if (isBlank(this._obj8)){this._obj8 = this._new(p._binding8);} return this._obj8;} - if (p._keyId9 === keyId) {if (isBlank(this._obj9)){this._obj9 = this._new(p._binding9);} return this._obj9;} + if (p._keyId0 === keyId && (p._visibility0 & visibility) > 0) {if (isBlank(this._obj0)){this._obj0 = this._new(p._binding0);} return this._obj0;} + if (p._keyId1 === keyId && (p._visibility1 & visibility) > 0) {if (isBlank(this._obj1)){this._obj1 = this._new(p._binding1);} return this._obj1;} + if (p._keyId2 === keyId && (p._visibility2 & visibility) > 0) {if (isBlank(this._obj2)){this._obj2 = this._new(p._binding2);} return this._obj2;} + if (p._keyId3 === keyId && (p._visibility3 & visibility) > 0) {if (isBlank(this._obj3)){this._obj3 = this._new(p._binding3);} return this._obj3;} + if (p._keyId4 === keyId && (p._visibility4 & visibility) > 0) {if (isBlank(this._obj4)){this._obj4 = this._new(p._binding4);} return this._obj4;} + if (p._keyId5 === keyId && (p._visibility5 & visibility) > 0) {if (isBlank(this._obj5)){this._obj5 = this._new(p._binding5);} return this._obj5;} + if (p._keyId6 === keyId && (p._visibility6 & visibility) > 0) {if (isBlank(this._obj6)){this._obj6 = this._new(p._binding6);} return this._obj6;} + if (p._keyId7 === keyId && (p._visibility7 & visibility) > 0) {if (isBlank(this._obj7)){this._obj7 = this._new(p._binding7);} return this._obj7;} + if (p._keyId8 === keyId && (p._visibility8 & visibility) > 0) {if (isBlank(this._obj8)){this._obj8 = this._new(p._binding8);} return this._obj8;} + if (p._keyId9 === keyId && (p._visibility9 & visibility) > 0) {if (isBlank(this._obj9)){this._obj9 = this._new(p._binding9);} return this._obj9;} + return _undefined; } diff --git a/modules/angular2/src/core/compiler/proto_view_factory.js b/modules/angular2/src/core/compiler/proto_view_factory.js index f22d252202..373410c60e 100644 --- a/modules/angular2/src/core/compiler/proto_view_factory.js +++ b/modules/angular2/src/core/compiler/proto_view_factory.js @@ -319,7 +319,7 @@ function _createProtoElementInjector(binderIndex, parentPeiWithDistance, renderE // so that, when hydrating, $implicit can be set to the element. var hasVariables = MapWrapper.size(renderElementBinder.variableBindings) > 0; if (directiveBindings.length > 0 || hasVariables) { - protoElementInjector = new ProtoElementInjector( + protoElementInjector = ProtoElementInjector.create( parentPeiWithDistance.protoElementInjector, binderIndex, directiveBindings, isPresent(componentDirectiveBinding), parentPeiWithDistance.distance diff --git a/modules/angular2/src/core/compiler/view_manager_utils.js b/modules/angular2/src/core/compiler/view_manager_utils.js index 263d11e9eb..2c1860c608 100644 --- a/modules/angular2/src/core/compiler/view_manager_utils.js +++ b/modules/angular2/src/core/compiler/view_manager_utils.js @@ -189,7 +189,7 @@ export class AppViewManagerUtils { for (var i = 0; i < binders.length; ++i) { var elementInjector = view.elementInjectors[i]; if (isPresent(elementInjector)) { - elementInjector.instantiateDirectives(appInjector, hostElementInjector, view.preBuiltObjects[i]); + elementInjector.hydrate(appInjector, hostElementInjector, view.preBuiltObjects[i]); this._setUpEventEmitters(view, elementInjector, i); this._setUpHostActions(view, elementInjector, i); @@ -238,7 +238,7 @@ export class AppViewManagerUtils { for (var i = 0; i < binders.length; ++i) { var elementInjector = view.elementInjectors[i]; if (isPresent(elementInjector)) { - elementInjector.clearDirectives(); + elementInjector.dehydrate(); } } if (isPresent(view.locals)) { diff --git a/modules/angular2/src/di/injector.ts b/modules/angular2/src/di/injector.ts index 99b13a4be5..c1d5d5be40 100644 --- a/modules/angular2/src/di/injector.ts +++ b/modules/angular2/src/di/injector.ts @@ -91,7 +91,7 @@ export class Injector { * `fromResolvedBindings` and `createChildFromResolved`. */ static resolve(bindings: List): List { - var resolvedBindings = _resolveBindings(bindings); + var resolvedBindings = resolveBindings(bindings); var flatten = _flattenBindings(resolvedBindings, MapWrapper.create()); return _createListOfBindings(flatten); } @@ -368,7 +368,7 @@ class _AsyncInjectorStrategy { } } -function _resolveBindings(bindings: List): List { +export function resolveBindings(bindings: List): List { var resolvedList = ListWrapper.createFixedSize(bindings.length); for (var i = 0; i < bindings.length; i++) { var unresolved = resolveForwardRef(bindings[i]); @@ -380,7 +380,7 @@ function _resolveBindings(bindings: List): List { } else if (unresolved instanceof Binding) { resolved = unresolved.resolve(); } else if (unresolved instanceof List) { - resolved = _resolveBindings(unresolved); + resolved = resolveBindings(unresolved); } else if (unresolved instanceof BindingBuilder) { throw new InvalidBindingError(unresolved.token); } else { @@ -391,6 +391,13 @@ function _resolveBindings(bindings: List): List { return resolvedList; } +function flattenBindings(bindings: List): List { + var map = _flattenBindings(bindings, MapWrapper.create()); + var res = ListWrapper.create(); + MapWrapper.forEach(map, (binding, keyId) => ListWrapper.push(res, binding)); + return res; +} + function _createListOfBindings(flattenedBindings): List { var bindings = ListWrapper.createFixedSize(Key.numberOfKeys + 1); MapWrapper.forEach(flattenedBindings, (v, keyId) => bindings[keyId] = v); diff --git a/modules/angular2/test/core/compiler/element_injector_spec.js b/modules/angular2/test/core/compiler/element_injector_spec.js index aeb62180da..0c60b63d46 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.js +++ b/modules/angular2/test/core/compiler/element_injector_spec.js @@ -2,11 +2,11 @@ import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, SpyObj import {isBlank, isPresent, IMPLEMENTS} from 'angular2/src/facade/lang'; import {ListWrapper, MapWrapper, List, StringMapWrapper, iterateListLike} from 'angular2/src/facade/collection'; import {ProtoElementInjector, ElementInjector, PreBuiltObjects, DirectiveBinding, TreeNode} - from 'angular2/src/core/compiler/element_injector'; -import {Parent, Ancestor} from 'angular2/src/core/annotations_impl/visibility'; + from 'angular2/src/core/compiler/element_injector'; +import {Parent, Ancestor, Unbounded} from 'angular2/src/core/annotations_impl/visibility'; import {Attribute, Query} from 'angular2/src/core/annotations_impl/di'; import {Component, Directive, onDestroy} from 'angular2/src/core/annotations_impl/annotations'; -import {bind, Injector} from 'angular2/di'; +import {bind, Injector, Binding} from 'angular2/di'; import {Optional, Inject} from 'angular2/src/di/annotations_impl'; import {AppProtoView, AppView} from 'angular2/src/core/compiler/view'; import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref'; @@ -16,8 +16,10 @@ import {DynamicChangeDetector, ChangeDetectorRef, Parser, Lexer} from 'angular2/ import {ViewRef, Renderer} from 'angular2/src/render/api'; import {QueryList} from 'angular2/src/core/compiler/query_list'; -class DummyDirective extends Directive { - constructor({lifecycle, events, hostActions} = {}) { super({lifecycle: lifecycle, events: events, hostActions:hostActions}); } +class DummyDirective extends Component { + constructor({lifecycle, events, hostActions, hostInjector, viewInjector} = {}) { + super({lifecycle: lifecycle, events: events, hostActions: hostActions, hostInjector: hostInjector, viewInjector: viewInjector}); + } } @proxy @@ -37,6 +39,9 @@ class DummyView extends SpyObject { class SimpleDirective { } +class SimpleService { +} + class SomeOtherDirective { } @@ -69,20 +74,27 @@ class OptionallyNeedsDirective { } } -class NeedDirectiveFromParent { +class NeedsDirectiveFromParent { dependency:SimpleDirective; constructor(@Parent() dependency:SimpleDirective){ this.dependency = dependency; } } -class NeedDirectiveFromAncestor { +class NeedsDirectiveFromAncestor { dependency:SimpleDirective; constructor(@Ancestor() dependency:SimpleDirective){ this.dependency = dependency; } } +class NeedsDirectiveFromAnAncestorShadowDom { + dependency:SimpleDirective; + constructor(@Unbounded() dependency:SimpleDirective){ + this.dependency = dependency; + } +} + class NeedsService { service:any; constructor(@Inject("service") service) { @@ -199,10 +211,19 @@ export function main() { var defaultPreBuiltObjects = new PreBuiltObjects(null, null, null); var appInjector = Injector.resolveAndCreate([]); + function createPei(parent, index, bindings, distance = 1, hasShadowRoot = false) { + var directiveBinding = ListWrapper.map(bindings, b => { + if (b instanceof DirectiveBinding) return b; + if (b instanceof Binding) return DirectiveBinding.createFromBinding(b, null); + return DirectiveBinding.createFromType(b, null); + }); + return ProtoElementInjector.create(parent, index, directiveBinding, hasShadowRoot, distance); + } + function humanize(tree, names:List) { var lookupName = (item) => - ListWrapper.last( - ListWrapper.find(names, (pair) => pair[0] === item)); + ListWrapper.last( + ListWrapper.find(names, (pair) => pair[0] === item)); if (tree.children.length == 0) return lookupName(tree); var children = tree.children.map(m => humanize(m, names)); @@ -210,56 +231,46 @@ export function main() { } function injector(bindings, lightDomAppInjector = null, - isComponent:bool = false, preBuiltObjects = null, attributes = null) { + isComponent:bool = false, preBuiltObjects = null, attributes = null) { if (isBlank(lightDomAppInjector)) lightDomAppInjector = appInjector; - var proto = new ProtoElementInjector(null, 0, bindings, isComponent); + var proto = createPei(null, 0, bindings, 0, isComponent); proto.attributes = attributes; - var inj = proto.instantiate(null); - var preBuilt = isPresent(preBuiltObjects) - ? preBuiltObjects - : defaultPreBuiltObjects; - inj.instantiateDirectives(lightDomAppInjector, null, preBuilt); + var inj = proto.instantiate(null); + var preBuilt = isPresent(preBuiltObjects) ? preBuiltObjects : defaultPreBuiltObjects; + inj.hydrate(lightDomAppInjector, null, preBuilt); return inj; } - function parentChildInjectors( - parentBindings, - childBindings, - parentPreBuildObjects = null, - isParentComponent:bool = false) { + function parentChildInjectors(parentBindings, childBindings, parentPreBuildObjects = null) { if (isBlank(parentPreBuildObjects)) parentPreBuildObjects = defaultPreBuiltObjects; var inj = Injector.resolveAndCreate([]); - var protoParent = new ProtoElementInjector(null, 0, parentBindings, isParentComponent); + + var protoParent = createPei(null, 0, parentBindings); var parent = protoParent.instantiate(null); - parent.instantiateDirectives(inj, null, parentPreBuildObjects); + parent.hydrate(inj, null, parentPreBuildObjects); - var protoChild = new ProtoElementInjector(protoParent, 1, childBindings, false, 1); + var protoChild = createPei(protoParent, 1, childBindings, 1, false); var child = protoChild.instantiate(parent); - child.instantiateDirectives(inj, null, defaultPreBuiltObjects); + child.hydrate(inj, null, defaultPreBuiltObjects); return child; } - function hostShadowInjectors( - hostBindings:List, - shadowBindings:List, - isParentComponent:bool = true, - isChildComponent:bool = false):ElementInjector { + function hostShadowInjectors(hostBindings:List, shadowBindings:List):ElementInjector { var inj = Injector.resolveAndCreate([]); - var protoParent = new ProtoElementInjector(null, 0, hostBindings, isParentComponent); - var host = protoParent.instantiate(null); - host.instantiateDirectives(inj, null, defaultPreBuiltObjects); + var protoHost = createPei(null, 0, hostBindings, 0, true); + var host = protoHost.instantiate(null); + host.hydrate(inj, null, defaultPreBuiltObjects); - var protoChild = new ProtoElementInjector(protoParent, 0, shadowBindings, - isChildComponent, 1); - var shadow = protoChild.instantiate(null); - shadow.instantiateDirectives(host.getShadowDomAppInjector(), host, null); + var protoShadow = createPei(null, 0, shadowBindings, 0, false); + var shadow = protoShadow.instantiate(null); + shadow.hydrate(host.getShadowDomAppInjector(), host, null); return shadow; } @@ -268,13 +279,13 @@ export function main() { var root, firstParent, lastParent, node; /* - Build a tree of the following shape: - root - - p1 - - c1 - - c2 - - p2 - - c3 + Build a tree of the following shape: + root + - p1 + - c1 + - c2 + - p2 + - c3 */ beforeEach(() => { root = new TestNode(null, 'root'); @@ -339,43 +350,45 @@ export function main() { }); }); + + describe("ProtoElementInjector", () => { describe("direct parent", () => { it("should return parent proto injector when distance is 1", () => { var distance = 1; - var protoParent = new ProtoElementInjector(null, 0, []); - var protoChild = new ProtoElementInjector(protoParent, 1, [], false, distance); + var protoParent = createPei(null, 0, []); + var protoChild = createPei(protoParent, 0, [], distance, false); expect(protoChild.directParent()).toEqual(protoParent); }); it("should return null otherwise", () => { var distance = 2; - var protoParent = new ProtoElementInjector(null, 0, []); - var protoChild = new ProtoElementInjector(protoParent, 1, [], false, distance); + var protoParent = createPei(null, 0, []); + var protoChild = createPei(protoParent, 0, [], distance, false); expect(protoChild.directParent()).toEqual(null); }); - it("should allow for direct access using getDirectiveBindingAtIndex", function () { + it("should allow for direct access using getBindingAtIndex", function () { var binding = DirectiveBinding.createFromBinding( - bind(SimpleDirective).toClass(SimpleDirective), null); - var proto = new ProtoElementInjector(null, 0, [binding]); + bind(SimpleDirective).toClass(SimpleDirective), null); + var proto = createPei(null, 0, [binding]); - expect(proto.getDirectiveBindingAtIndex(0)).toBeAnInstanceOf(DirectiveBinding); - expect(() => proto.getDirectiveBindingAtIndex(-1)).toThrowError( - 'Index -1 is out-of-bounds.'); - expect(() => proto.getDirectiveBindingAtIndex(10)).toThrowError( - 'Index 10 is out-of-bounds.'); + expect(proto.getBindingAtIndex(0)).toBeAnInstanceOf(DirectiveBinding); + expect(() => proto.getBindingAtIndex(-1)).toThrowError( + 'Index -1 is out-of-bounds.'); + expect(() => proto.getBindingAtIndex(10)).toThrowError( + 'Index 10 is out-of-bounds.'); }); }); describe('event emitters', () => { it('should return a list of event accessors', () => { var binding = DirectiveBinding.createFromType( - HasEventEmitter, new DummyDirective({events: ['emitter']})); + HasEventEmitter, new DummyDirective({events: ['emitter']})); - var inj = new ProtoElementInjector(null, 0, [binding]); + var inj = createPei(null, 0, [binding]); expect(inj.eventEmitterAccessors.length).toEqual(1); var accessor = inj.eventEmitterAccessors[0][0]; @@ -385,9 +398,9 @@ export function main() { it('should return a list of hostAction accessors', () => { var binding = DirectiveBinding.createFromType( - HasEventEmitter, new DummyDirective({hostActions: {'hostActionName' : 'onAction'}})); + HasEventEmitter, new DummyDirective({hostActions: {'hostActionName' : 'onAction'}})); - var inj = new ProtoElementInjector(null, 0, [binding]); + var inj = createPei(null, 0, [binding]); expect(inj.hostActionAccessors.length).toEqual(1); var accessor = inj.hostActionAccessors[0][0]; @@ -395,14 +408,41 @@ export function main() { expect(accessor.getter(new HasHostAction())).toEqual('hostAction'); }); }); + + + describe(".create", () => { + it("should collect hostInjector injectables from all directives", () => { + var pei = createPei(null, 0, [ + DirectiveBinding.createFromType(SimpleDirective, + new DummyDirective({hostInjector: [bind('injectable1').toValue('injectable1')]})), + DirectiveBinding.createFromType(SomeOtherDirective, + new DummyDirective({hostInjector: [bind('injectable2').toValue('injectable2')]})) + ]); + + expect(pei.getBindingAtIndex(0).key.token).toBe(SimpleDirective); + expect(pei.getBindingAtIndex(1).key.token).toBe(SomeOtherDirective); + expect(pei.getBindingAtIndex(2).key.token).toEqual("injectable1"); + expect(pei.getBindingAtIndex(3).key.token).toEqual("injectable2"); + }); + + it("should collect viewInjector injectables from the component", () => { + var pei = createPei(null, 0, [ + DirectiveBinding.createFromType(SimpleDirective, + new DummyDirective({viewInjector: [bind('injectable1').toValue('injectable1')]})) + ], 0, true); + + expect(pei.getBindingAtIndex(0).key.token).toBe(SimpleDirective); + expect(pei.getBindingAtIndex(1).key.token).toEqual("injectable1"); + }); + }); }); describe("ElementInjector", function () { describe("instantiate", function () { it("should create an element injector", function () { - var protoParent = new ProtoElementInjector(null, 0, []); - var protoChild1 = new ProtoElementInjector(protoParent, 1, []); - var protoChild2 = new ProtoElementInjector(protoParent, 2, []); + var protoParent = createPei(null, 0, []); + var protoChild1 = createPei(protoParent, 1, []); + var protoChild2 = createPei(protoParent, 2, []); var p = protoParent.instantiate(null); var c1 = protoChild1.instantiate(p); @@ -418,8 +458,8 @@ export function main() { describe("direct parent", () => { it("should return parent injector when distance is 1", () => { var distance = 1; - var protoParent = new ProtoElementInjector(null, 0, []); - var protoChild = new ProtoElementInjector(protoParent, 1, [], false, distance); + var protoParent = createPei(null, 0, []); + var protoChild = createPei(protoParent, 1, [], distance); var p = protoParent.instantiate(null); var c = protoChild.instantiate(p); @@ -429,8 +469,8 @@ export function main() { it("should return null otherwise", () => { var distance = 2; - var protoParent = new ProtoElementInjector(null, 0, []); - var protoChild = new ProtoElementInjector(protoParent, 1, [], false, distance); + var protoParent = createPei(null, 0, []); + var protoChild = createPei(protoParent, 1, [], distance); var p = protoParent.instantiate(null); var c = protoChild.instantiate(p); @@ -442,12 +482,12 @@ export function main() { describe("hasBindings", function () { it("should be true when there are bindings", function () { - var p = new ProtoElementInjector(null, 0, [SimpleDirective]); + var p = createPei(null, 0, [SimpleDirective]); expect(p.hasBindings).toBeTruthy(); }); it("should be false otherwise", function () { - var p = new ProtoElementInjector(null, 0, []); + var p = createPei(null, 0, []); expect(p.hasBindings).toBeFalsy(); }); }); @@ -462,7 +502,7 @@ export function main() { }); }); - describe("instantiateDirectives", function () { + describe("hydrate", function () { it("should instantiate directives that have no dependencies", function () { var inj = injector([SimpleDirective]); expect(inj.get(SimpleDirective)).toBeAnInstanceOf(SimpleDirective); @@ -495,52 +535,6 @@ export function main() { expect(inj.get(NeedsProtoViewRef).protoViewRef).toEqual(new ProtoViewRef(protoView)); }); - 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") - ] - }); - 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: [ - bind("service").toValue("service") - ] - }); - var componentDirective = DirectiveBinding.createFromType(SimpleDirective, directiveAnnotation); - expect(() => { - injector([componentDirective, NeedsService], null); - }).toThrowError('No provider for service! (NeedsService -> service)'); - }); - it("should return app services", function () { var appInjector = Injector.resolveAndCreate([ bind("service").toValue("service") @@ -551,41 +545,51 @@ export function main() { }); it("should get directives from parent", function () { - var child = parentChildInjectors([SimpleDirective], [NeedDirectiveFromParent]); + var child = parentChildInjectors([SimpleDirective], [NeedsDirectiveFromParent]); - var d = child.get(NeedDirectiveFromParent); + var d = child.get(NeedsDirectiveFromParent); - expect(d).toBeAnInstanceOf(NeedDirectiveFromParent); + expect(d).toBeAnInstanceOf(NeedsDirectiveFromParent); expect(d.dependency).toBeAnInstanceOf(SimpleDirective); }); it("should not return parent's directives on self", function () { expect(() => { - injector([SimpleDirective, NeedDirectiveFromParent]); + injector([SimpleDirective, NeedsDirectiveFromParent]); }).toThrowError(new RegExp("No provider for SimpleDirective")); }); it("should get directives from ancestor", function () { - var child = parentChildInjectors([SimpleDirective], [NeedDirectiveFromAncestor]); + var child = parentChildInjectors([SimpleDirective], [NeedsDirectiveFromAncestor]); - var d = child.get(NeedDirectiveFromAncestor); + var d = child.get(NeedsDirectiveFromAncestor); - expect(d).toBeAnInstanceOf(NeedDirectiveFromAncestor); + expect(d).toBeAnInstanceOf(NeedsDirectiveFromAncestor); expect(d.dependency).toBeAnInstanceOf(SimpleDirective); }); - it("should throw when no SimpleDirective found", function () { - expect(() => injector([NeedDirectiveFromParent])). - toThrowError('No provider for SimpleDirective! (NeedDirectiveFromParent -> 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 inject null when no directive found", function () { + 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 SimpleDirective bindings instead of SimpleDirective types", function () { + it("should accept bindings instead types", function () { var inj = injector([ DirectiveBinding.createFromBinding(bind(SimpleDirective).toClass(SimpleDirective), null) ]); @@ -598,11 +602,12 @@ export function main() { ]); expect(inj.getDirectiveAtIndex(0)).toBeAnInstanceOf(SimpleDirective); expect(() => inj.getDirectiveAtIndex(-1)).toThrowError( - 'Index -1 is out-of-bounds.'); + 'Index -1 is out-of-bounds.'); expect(() => inj.getDirectiveAtIndex(10)).toThrowError( - 'Index 10 is out-of-bounds.'); + '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]); @@ -612,56 +617,107 @@ export function main() { DirectiveBinding.createFromBinding(bBneedsA, null) ]); }).toThrowError('Cannot instantiate cyclic dependency! ' + - '(A_Needs_B -> B_Needs_A -> A_Needs_B)'); + '(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") + ] + }); + 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: [ + bind("service").toValue("service") + ] + }); + 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 inj = injector([DirectiveBinding.createFromType(DirectiveWithDestroy, new DummyDirective({lifecycle: [onDestroy]}))]); var destroy = inj.get(DirectiveWithDestroy); - inj.clearDirectives(); + 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), null); + + 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(NeedDirectiveFromAncestor, null), null); - expect(inj.getDynamicallyLoadedComponent()).toBeAnInstanceOf(NeedDirectiveFromAncestor); + 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), null); + injWithDynamicallyLoadedComponent.dynamicallyCreateComponent(DirectiveBinding.createFromType(SomeOtherDirective, null), appInjector); - var shadowDomProtoInjector = new ProtoElementInjector(null, 0, [NeedDirectiveFromAncestor], false); + var shadowDomProtoInjector = createPei(null, 0, [NeedsDirectiveFromAncestor]); var shadowDomInj = shadowDomProtoInjector.instantiate(null); expect(() => - shadowDomInj.instantiateDirectives(appInjector, injWithDynamicallyLoadedComponent, defaultPreBuiltObjects)). - toThrowError(new RegExp("No provider for SimpleDirective")); + 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 = new ProtoElementInjector(null, 0, [dynamicComp, NeedsDirective], true); + var proto = createPei(null, 0, [dynamicComp, NeedsDirective], 1, true); var inj = proto.instantiate(null); inj.dynamicallyCreateComponent( - DirectiveBinding.createFromType(SimpleDirective, null), null); + DirectiveBinding.createFromType(SimpleDirective, null), appInjector); var error = null; try { - inj.instantiateDirectives(Injector.resolveAndCreate([]), null, null); + inj.hydrate(Injector.resolveAndCreate([]), null, null); } catch(e) { error = e; } @@ -672,32 +728,32 @@ export function main() { 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, null); + injWithDynamicallyLoadedComponent.dynamicallyCreateComponent(componentDirective, appInjector); - var shadowDomProtoInjector = new ProtoElementInjector(null, 0, [NeedDirectiveFromAncestor], false); + var shadowDomProtoInjector = createPei(null, 0, [NeedsDirectiveFromAncestor]); var shadowDomInjector = shadowDomProtoInjector.instantiate(null); - shadowDomInjector.instantiateDirectives(appInjector, injWithDynamicallyLoadedComponent, defaultPreBuiltObjects); + shadowDomInjector.hydrate(appInjector, injWithDynamicallyLoadedComponent, defaultPreBuiltObjects); - expect(shadowDomInjector.get(NeedDirectiveFromAncestor)).toBeAnInstanceOf(NeedDirectiveFromAncestor); - expect(shadowDomInjector.get(NeedDirectiveFromAncestor).dependency).toBeAnInstanceOf(SimpleDirective); + 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]}) - ), - null); + DirectiveBinding.createFromType( + DirectiveWithDestroy, + new DummyDirective({lifecycle: [onDestroy]}) + ), + appInjector); var dir = inj.getDynamicallyLoadedComponent(); - inj.clearDirectives(); + inj.dehydrate(); expect(inj.getDynamicallyLoadedComponent()).toBe(null); expect(dir.onDestroyCounter).toBe(1); - inj.instantiateDirectives(null, null, null); + inj.hydrate(null, null, null); expect(inj.getDynamicallyLoadedComponent()).toBe(null); }); @@ -766,7 +822,7 @@ export function main() { it("should throw if there is no ProtoViewRef", function () { expect( - () => injector([NeedsProtoViewRef]) + () => injector([NeedsProtoViewRef]) ).toThrowError('No provider for ProtoViewRef! (NeedsProtoViewRef -> ProtoViewRef)'); }); @@ -814,25 +870,25 @@ export function main() { //}); it('should contain directives on the same and a child injector in construction order', () => { - var protoParent = new ProtoElementInjector(null, 0, [NeedsQuery, CountingDirective]); - var protoChild = new ProtoElementInjector(protoParent, 1, [CountingDirective]); + var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]); + var protoChild = createPei(protoParent, 1, [CountingDirective]); var parent = protoParent.instantiate(null); var child = protoChild.instantiate(parent); - parent.instantiateDirectives(Injector.resolveAndCreate([]), null, preBuildObjects); - child.instantiateDirectives(Injector.resolveAndCreate([]), null, preBuildObjects); + 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 = new ProtoElementInjector(null, 0, [NeedsQuery, CountingDirective]); - var protoChild = new ProtoElementInjector(protoParent, 1, [CountingDirective]); + var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]); + var protoChild = createPei(protoParent, 1, [CountingDirective]); var parent = protoParent.instantiate(null); var child = protoChild.instantiate(parent); - parent.instantiateDirectives(Injector.resolveAndCreate([]), null, preBuildObjects); - child.instantiateDirectives(Injector.resolveAndCreate([]), null, preBuildObjects); + parent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + child.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); child.unlink(); @@ -840,17 +896,17 @@ export function main() { }); it('should reflect moving an injector as a last child', () => { - var protoParent = new ProtoElementInjector(null, 0, [NeedsQuery, CountingDirective]); - var protoChild1 = new ProtoElementInjector(protoParent, 1, [CountingDirective]); - var protoChild2 = new ProtoElementInjector(protoParent, 1, [CountingDirective]); + 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.instantiateDirectives(Injector.resolveAndCreate([]), null, preBuildObjects); - child1.instantiateDirectives(Injector.resolveAndCreate([]), null, preBuildObjects); - child2.instantiateDirectives(Injector.resolveAndCreate([]), null, preBuildObjects); + parent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + child1.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + child2.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); child1.unlink(); child1.link(parent); @@ -860,17 +916,17 @@ export function main() { }); it('should reflect moving an injector as a first child', () => { - var protoParent = new ProtoElementInjector(null, 0, [NeedsQuery, CountingDirective]); - var protoChild1 = new ProtoElementInjector(protoParent, 1, [CountingDirective]); - var protoChild2 = new ProtoElementInjector(protoParent, 1, [CountingDirective]); + 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.instantiateDirectives(Injector.resolveAndCreate([]), null, preBuildObjects); - child1.instantiateDirectives(Injector.resolveAndCreate([]), null, preBuildObjects); - child2.instantiateDirectives(Injector.resolveAndCreate([]), null, preBuildObjects); + parent.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + child1.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); + child2.hydrate(Injector.resolveAndCreate([]), null, preBuildObjects); child2.unlink(); child2.linkAfter(parent, null); @@ -880,17 +936,17 @@ export function main() { }); it('should support two concurrent queries for the same directive', () => { - var protoGrandParent = new ProtoElementInjector(null, 0, [NeedsQuery]); - var protoParent = new ProtoElementInjector(null, 0, [NeedsQuery]); - var protoChild = new ProtoElementInjector(protoParent, 1, [CountingDirective]); + 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.instantiateDirectives(Injector.resolveAndCreate([]), null, preBuildObjects); - parent.instantiateDirectives(Injector.resolveAndCreate([]), null, preBuildObjects); - child.instantiateDirectives(Injector.resolveAndCreate([]), null, preBuildObjects); + 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; diff --git a/modules/angular2/test/core/compiler/integration_spec.js b/modules/angular2/test/core/compiler/integration_spec.js index 9aa15376ca..bd3e44279e 100644 --- a/modules/angular2/test/core/compiler/integration_spec.js +++ b/modules/angular2/test/core/compiler/integration_spec.js @@ -29,7 +29,7 @@ import {PipeRegistry, defaultPipeRegistry, import {Directive, Component} from 'angular2/src/core/annotations_impl/annotations'; import {QueryList} from 'angular2/src/core/compiler/query_list'; import {View} from 'angular2/src/core/annotations_impl/view'; -import {Parent, Ancestor} from 'angular2/src/core/annotations_impl/visibility'; +import {Parent, Ancestor, Unbounded} from 'angular2/src/core/annotations_impl/visibility'; import {Attribute, Query} from 'angular2/src/core/annotations_impl/di'; import {NgIf} from 'angular2/src/directives/ng_if'; @@ -822,6 +822,68 @@ export function main() { })); }); + describe("dependency injection", () => { + it("should support hostInjector", inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideView(MyComp, new View({ + template: ` + + + + + `, + directives: [DirectiveProvidingInjectable, DirectiveConsumingInjectable] + })); + tb.createView(MyComp, {context: ctx}).then((view) => { + var comp = view.rawView.locals.get("consuming"); + expect(comp.injectable).toBeAnInstanceOf(Injectable); + + async.done(); + }); + })); + + it("should support viewInjector", inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideView(DirectiveProvidingInjectableInView, new View({ + template: ` + + + `, + directives: [DirectiveConsumingInjectable] + })); + tb.createView(DirectiveProvidingInjectableInView, {context: new DirectiveProvidingInjectableInView()}).then((view) => { + var comp = view.rawView.locals.get("consuming"); + expect(comp.injectable).toBeAnInstanceOf(Injectable); + + async.done(); + }); + })); + + it("should support unbounded lookup", inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideView(MyComp, new View({ + template: ` + + + + + `, + directives: [DirectiveProvidingInjectable, DirectiveContainingDirectiveConsumingAnInjectable] + })); + + tb.overrideView(DirectiveContainingDirectiveConsumingAnInjectable, new View({ + template: ` + + `, + directives: [DirectiveConsumingInjectableUnbounded] + })); + + tb.createView(MyComp, {context: ctx}).then((view) => { + var comp = view.rawView.locals.get("dir"); + expect(comp.directive.injectable).toBeAnInstanceOf(Injectable); + + async.done(); + }); + })); + }); + describe("error handling", () => { it('should report a meaningful error when a directive is missing annotation', inject([TestBed, AsyncTestCompleter], (tb, async) => { @@ -1477,3 +1539,57 @@ class DirectiveWithTwoWayBinding { ObservableWrapper.callNext(this.control, value); } } + +class Injectable {} + +@Directive({ + selector: 'directive-providing-injectable', + hostInjector: [Injectable] +}) +class DirectiveProvidingInjectable { +} + +@Component({ + selector: 'directive-providing-injectable', + viewInjector: [Injectable] +}) +@View({template:''}) +class DirectiveProvidingInjectableInView { +} + +@Component({ + selector: 'directive-consuming-injectable' +}) +@View({template:''}) +class DirectiveConsumingInjectable { + injectable; + + constructor(@Ancestor() injectable:Injectable) { + this.injectable = injectable; + } +} + + + +@Component({ + selector: 'directive-containing-directive-consuming-an-injectable' +}) +class DirectiveContainingDirectiveConsumingAnInjectable { + directive; +} + +@Component({ + selector: 'directive-consuming-injectable-unbounded' +}) +@View({template:''}) +class DirectiveConsumingInjectableUnbounded { + injectable; + + constructor( + @Unbounded() injectable:Injectable, + @Ancestor() parent:DirectiveContainingDirectiveConsumingAnInjectable) { + + this.injectable = injectable; + parent.directive = this; + } +} diff --git a/modules/angular2/test/core/compiler/view_manager_utils_spec.js b/modules/angular2/test/core/compiler/view_manager_utils_spec.js index 80f7924cc9..42ffa7b171 100644 --- a/modules/angular2/test/core/compiler/view_manager_utils_spec.js +++ b/modules/angular2/test/core/compiler/view_manager_utils_spec.js @@ -131,14 +131,14 @@ export function main() { // (() => () nonsense is required until our transpiler supports type casting var spyEi = (() => componentView.elementInjectors[0])(); - spyEi.spy('instantiateDirectives').andCallFake(log.fn('instantiateDirectives')); + spyEi.spy('hydrate').andCallFake(log.fn('hydrate')); var spyCd = (() => componentView.changeDetector)(); spyCd.spy('hydrate').andCallFake(log.fn('hydrateCD')); utils.hydrateComponentView(hostView, 0) - expect(log.result()).toEqual('instantiateDirectives; hydrateCD'); + expect(log.result()).toEqual('hydrate; hydrateCD'); }); }); @@ -261,7 +261,7 @@ export function main() { createViews(); utils.hydrateViewInContainer(parentView, 0, contextView, 0, 0, null); - expect(childView.rootElementInjectors[0].spy('instantiateDirectives')) + expect(childView.rootElementInjectors[0].spy('hydrate')) .toHaveBeenCalledWith(null, contextView.elementInjectors[0].getHost(), childView.preBuiltObjects[0]); }); @@ -282,7 +282,7 @@ export function main() { createViews(); utils.hydrateRootHostView(hostView, injector); - expect(hostView.rootElementInjectors[0].spy('instantiateDirectives')) + expect(hostView.rootElementInjectors[0].spy('hydrate')) .toHaveBeenCalledWith(injector, null, hostView.preBuiltObjects[0]); }); diff --git a/modules/angular2/test/core/forward_ref_integration_spec.es6 b/modules/angular2/test/core/forward_ref_integration_spec.es6 index c06835af89..9b802baca4 100644 --- a/modules/angular2/test/core/forward_ref_integration_spec.es6 +++ b/modules/angular2/test/core/forward_ref_integration_spec.es6 @@ -35,7 +35,7 @@ export function main() { @Component({ selector: 'app', - injectables: [ + appInjector: [ forwardRef(() => Frame) ] }) diff --git a/modules/benchmarks/e2e_test/element_injector_perf.es6 b/modules/benchmarks/e2e_test/element_injector_perf.es6 index a8eae2a5bc..4e09f7e9fc 100644 --- a/modules/benchmarks/e2e_test/element_injector_perf.es6 +++ b/modules/benchmarks/e2e_test/element_injector_perf.es6 @@ -20,11 +20,11 @@ describe('ng2 element injector benchmark', function () { }).then(done, done.fail); }); - it('should log the stats for instantiateDirectives', function(done) { + it('should log the stats for hydrate', function(done) { perfUtil.runClickBenchmark({ url: URL, - buttons: ['#instantiateDirectives'], - id: 'ng2.elementInjector.instantiateDirectives', + buttons: ['#hydrate'], + id: 'ng2.elementInjector.hydrate', params: [{ name: 'iterations', value: 20000, scale: 'linear' }], diff --git a/modules/benchmarks/src/element_injector/element_injector_benchmark.html b/modules/benchmarks/src/element_injector/element_injector_benchmark.html index 9098d9a733..409d5fb7a0 100644 --- a/modules/benchmarks/src/element_injector/element_injector_benchmark.html +++ b/modules/benchmarks/src/element_injector/element_injector_benchmark.html @@ -13,7 +13,7 @@

Actions

- +

$SCRIPTS$ diff --git a/modules/benchmarks/src/element_injector/element_injector_benchmark.js b/modules/benchmarks/src/element_injector/element_injector_benchmark.js index 66517fba21..d34ec94cb7 100644 --- a/modules/benchmarks/src/element_injector/element_injector_benchmark.js +++ b/modules/benchmarks/src/element_injector/element_injector_benchmark.js @@ -1,7 +1,7 @@ import {reflector} from 'angular2/src/reflection/reflection'; import {ReflectionCapabilities} from 'angular2/src/reflection/reflection_capabilities'; import {Injectable, Injector} from 'angular2/di'; -import {ProtoElementInjector} from 'angular2/src/core/compiler/element_injector'; +import {ProtoElementInjector, DirectiveBinding} from 'angular2/src/core/compiler/element_injector'; import {getIntParameter, bindAction, microBenchmark} from 'angular2/src/test_lib/benchmark_util'; import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter'; @@ -14,21 +14,25 @@ export function main() { reflector.reflectionCapabilities = new ReflectionCapabilities(); var appInjector = Injector.resolveAndCreate([]); - var bindings = [A, B, C]; - var proto = new ProtoElementInjector(null, 0, bindings); + var bindings = [ + DirectiveBinding.createFromType(A, null), + DirectiveBinding.createFromType(B, null), + DirectiveBinding.createFromType(C, null) + ]; + var proto = ProtoElementInjector.create(null, 0, bindings, false, 0); var elementInjector = proto.instantiate(null); function instantiate () { for (var i = 0; i < iterations; ++i) { var ei = proto.instantiate(null); - ei.instantiateDirectives(appInjector, null, null); + ei.hydrate(appInjector, null, null); } } - function instantiateDirectives () { + function hydrate () { for (var i = 0; i < iterations; ++i) { - elementInjector.clearDirectives(); - elementInjector.instantiateDirectives(appInjector, null, null); + elementInjector.dehydrate(); + elementInjector.hydrate(appInjector, null, null); } } @@ -37,8 +41,8 @@ export function main() { () => microBenchmark('instantiateAvg', iterations, instantiate) ); bindAction( - '#instantiateDirectives', - () => microBenchmark('instantiateAvg', iterations, instantiateDirectives) + '#hydrate', + () => microBenchmark('instantiateAvg', iterations, hydrate) ); }