feat(di): added hostInjector and viewInjector to the Directive annotation

This commit is contained in:
vsavkin 2015-05-16 11:01:02 -07:00
parent 7b511462af
commit b066b8d15a
14 changed files with 877 additions and 439 deletions

View File

@ -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 {

View File

@ -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: `<needs-greeter></needs-greeter>`,
* 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;
}
}

View File

@ -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:
*
* ```
*<div dependency="1" my-directive></div>
* ```
*
* @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);
}
}

View File

@ -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<ResolvedBinding>;
resolvedAppInjectables:List<ResolvedBinding>;
resolvedHostInjectables:List<ResolvedBinding>;
resolvedViewInjectables:List<ResolvedBinding>;
metadata: DirectiveMetadata;
constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean,
resolvedInjectables:List<ResolvedBinding>, metadata:DirectiveMetadata, annotation: Directive) {
resolvedAppInjectables:List<ResolvedBinding>, resolvedHostInjectables:List<ResolvedBinding>,
resolvedViewInjectables:List<ResolvedBinding>, 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<List<EventEmitterAccessor>>;
hostActionAccessors:List<List<HostActionAccessor>>;
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<BindingData>, 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;
}

View File

@ -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

View File

@ -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)) {

View File

@ -91,7 +91,7 @@ export class Injector {
* `fromResolvedBindings` and `createChildFromResolved`.
*/
static resolve(bindings: List<any>): List<ResolvedBinding> {
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<any>): List<ResolvedBinding> {
export function resolveBindings(bindings: List<any>): List<ResolvedBinding> {
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<any>): List<ResolvedBinding> {
} 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<any>): List<ResolvedBinding> {
return resolvedList;
}
function flattenBindings(bindings: List<ResolvedBinding>): List<ResolvedBinding> {
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<any> {
var bindings = ListWrapper.createFixedSize(Key.numberOfKeys + 1);
MapWrapper.forEach(flattenedBindings, (v, keyId) => bindings[keyId] = v);

View File

@ -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;

View File

@ -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: `
<directive-providing-injectable>
<directive-consuming-injectable #consuming>
</directive-consuming-injectable>
</directive-providing-injectable>
`,
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: `
<directive-consuming-injectable #consuming>
</directive-consuming-injectable>
`,
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: `
<directive-providing-injectable>
<directive-containing-directive-consuming-an-injectable #dir>
</directive-containing-directive-consuming-an-injectable>
</directive-providing-injectable>
`,
directives: [DirectiveProvidingInjectable, DirectiveContainingDirectiveConsumingAnInjectable]
}));
tb.overrideView(DirectiveContainingDirectiveConsumingAnInjectable, new View({
template: `
<directive-consuming-injectable-unbounded></directive-consuming-injectable-unbounded>
`,
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;
}
}

View File

@ -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]);
});

View File

@ -35,7 +35,7 @@ export function main() {
@Component({
selector: 'app',
injectables: [
appInjector: [
forwardRef(() => Frame)
]
})

View File

@ -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'
}],

View File

@ -13,7 +13,7 @@
<h2>Actions</h2>
<p>
<button id="instantiate">instantiate</button>
<button id="instantiateDirectives">instantiateDirectives</button>
<button id="hydrate">hydrate</button>
</p>
$SCRIPTS$

View File

@ -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)
);
}