angular2
Version:
Angular 2 - a web framework for modern web apps
1,060 lines (914 loc) • 36.2 kB
JavaScript
import {isPresent, isBlank, Type, int, BaseException} from 'angular2/src/facade/lang';
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
import {Math} from 'angular2/src/facade/math';
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {Injector, Key, Dependency, bind, Binding, ResolvedBinding, NoBindingError,
AbstractBindingError, CyclicDependencyError} from 'angular2/di';
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
import {PropertySetter, Attribute, Query} from 'angular2/src/core/annotations/di';
import * as viewModule from 'angular2/src/core/compiler/view';
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {NgElement} from 'angular2/src/core/compiler/ng_element';
import {Directive, Component, onChange, onDestroy, onAllChangesDone} from 'angular2/src/core/annotations/annotations';
import {ChangeDetector, ChangeDetectorRef} from 'angular2/change_detection';
import {QueryList} from './query_list';
import {reflector} from 'angular2/src/reflection/reflection';
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;
var MAX_DEPTH = Math.pow(2, 30) - 1;
var _undefined = new Object();
var _staticKeys;
/**
* @exportedAs angular2/view
*/
export class ElementRef {
hostView:viewModule.AppView;
boundElementIndex:number;
injector:Injector;
elementInjector:ElementInjector;
constructor(elementInjector, hostView, boundElementIndex, injector){
this.elementInjector = elementInjector;
this.hostView = hostView;
this.boundElementIndex = boundElementIndex;
this.injector = injector;
}
get viewContainer() {
return this.hostView.getOrCreateViewContainer(this.boundElementIndex);
}
}
class StaticKeys {
viewId:number;
ngElementId:number;
viewContainerId:number;
changeDetectorRefId:number;
elementRefId:number;
constructor() {
//TODO: vsavkin Key.annotate(Key.get(AppView), 'static')
this.viewId = Key.get(viewModule.AppView).id;
this.ngElementId = Key.get(NgElement).id;
this.viewContainerId = Key.get(ViewContainer).id;
this.changeDetectorRefId = Key.get(ChangeDetectorRef).id;
this.elementRefId = Key.get(ElementRef).id;
}
static instance() {
if (isBlank(_staticKeys)) _staticKeys = new StaticKeys();
return _staticKeys;
}
}
export class TreeNode {
_parent:TreeNode;
_head:TreeNode;
_tail:TreeNode;
_next:TreeNode;
constructor(parent:TreeNode) {
this._head = null;
this._tail = null;
this._next = null;
if (isPresent(parent)) parent.addChild(this);
}
_assertConsistency() {
this._assertHeadBeforeTail();
this._assertTailReachable();
this._assertPresentInParentList();
}
_assertHeadBeforeTail() {
if (isBlank(this._tail) && isPresent(this._head)) throw new BaseException('null tail but non-null head');
}
_assertTailReachable() {
if (isBlank(this._tail)) return;
if (isPresent(this._tail._next)) throw new BaseException('node after tail');
var p = this._head;
while (isPresent(p) && p != this._tail) p = p._next;
if (isBlank(p) && isPresent(this._tail)) throw new BaseException('tail not reachable.')
}
_assertPresentInParentList() {
var p = this._parent;
if (isBlank(p)) {
return;
}
var cur = p._head;
while (isPresent(cur) && cur != this) cur = cur._next;
if (isBlank(cur)) throw new BaseException('node not reachable through parent.')
}
/**
* Adds a child to the parent node. The child MUST NOT be a part of a tree.
*/
addChild(child:TreeNode) {
if (isPresent(this._tail)) {
this._tail._next = child;
this._tail = child;
} else {
this._tail = this._head = child;
}
child._next = null;
child._parent = this;
this._assertConsistency();
}
/**
* Adds a child to the parent node after a given sibling.
* The child MUST NOT be a part of a tree and the sibling must be present.
*/
addChildAfter(child:TreeNode, prevSibling:TreeNode) {
this._assertConsistency();
if (isBlank(prevSibling)) {
var prevHead = this._head;
this._head = child;
child._next = prevHead;
if (isBlank(this._tail)) this._tail = child;
} else if (isBlank(prevSibling._next)) {
this.addChild(child);
return;
} else {
prevSibling._assertPresentInParentList();
child._next = prevSibling._next;
prevSibling._next = child;
}
child._parent = this;
this._assertConsistency();
}
/**
* Detaches a node from the parent's tree.
*/
remove() {
this._assertConsistency();
if (isBlank(this.parent)) return;
var nextSibling = this._next;
var prevSibling = this._findPrev();
if (isBlank(prevSibling)) {
this.parent._head = this._next;
} else {
prevSibling._next = this._next;
}
if (isBlank(nextSibling)) {
this._parent._tail = prevSibling;
}
this._parent._assertConsistency();
this._parent = null;
this._next = null;
this._assertConsistency();
}
/**
* Finds a previous sibling or returns null if first child.
* Assumes the node has a parent.
* TODO(rado): replace with DoublyLinkedList to avoid O(n) here.
*/
_findPrev() {
var node = this.parent._head;
if (node == this) return null;
while (node._next !== this) node = node._next;
return node;
}
get parent() {
return this._parent;
}
// TODO(rado): replace with a function call, does too much work for a getter.
get children() {
var res = [];
var child = this._head;
while (child != null) {
ListWrapper.push(res, child);
child = child._next;
}
return res;
}
}
export class DirectiveDependency extends Dependency {
depth:int;
propSetterName:string;
attributeName:string;
queryDirective;
constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean, properties:List,
depth:int, propSetterName: string, attributeName:string, queryDirective) {
super(key, asPromise, lazy, optional, properties);
this.depth = depth;
this.propSetterName = propSetterName;
this.attributeName = attributeName;
this.queryDirective = queryDirective;
this._verify();
}
_verify() {
var count = 0;
if (isPresent(this.propSetterName)) count++;
if (isPresent(this.queryDirective)) count++;
if (isPresent(this.attributeName)) count++;
if (count > 1) throw new BaseException(
'A directive injectable can contain only one of the following @PropertySetter, @Attribute or @Query.');
}
static createFrom(d:Dependency):Dependency {
return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional,
d.properties, DirectiveDependency._depth(d.properties),
DirectiveDependency._propSetterName(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 _propSetterName(properties):string {
var p = ListWrapper.find(properties, (p) => p instanceof PropertySetter);
return isPresent(p) ? p.propName : null;
}
static _attributeName(properties):string {
var p = ListWrapper.find(properties, (p) => p instanceof Attribute);
return isPresent(p) ? p.attributeName : null;
}
static _query(properties) {
var p = ListWrapper.find(properties, (p) => p instanceof Query);
return isPresent(p) ? p.directive : null;
}
}
export class DirectiveBinding extends ResolvedBinding {
callOnDestroy:boolean;
callOnChange:boolean;
callOnAllChangesDone:boolean;
annotation:Directive;
resolvedInjectables:List<ResolvedBinding>;
constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean, annotation:Directive) {
super(key, factory, dependencies, providedAsPromise);
this.callOnDestroy = isPresent(annotation) && annotation.hasLifecycleHook(onDestroy);
this.callOnChange = isPresent(annotation) && annotation.hasLifecycleHook(onChange);
this.callOnAllChangesDone = isPresent(annotation) && annotation.hasLifecycleHook(onAllChangesDone);
this.annotation = annotation;
if (annotation instanceof Component && isPresent(annotation.injectables)) {
this.resolvedInjectables = Injector.resolve(annotation.injectables);
}
}
get eventEmitters():List<string> {
return isPresent(this.annotation) && isPresent(this.annotation.events) ? this.annotation.events : [];
}
get changeDetection() {
if (this.annotation instanceof Component) {
var c:Component = this.annotation;
return c.changeDetection;
} else {
return null;
}
}
static createFromBinding(b:Binding, annotation:Directive):DirectiveBinding {
var rb = b.resolve();
var deps = ListWrapper.map(rb.dependencies, DirectiveDependency.createFrom);
return new DirectiveBinding(rb.key, rb.factory, deps, rb.providedAsPromise, annotation);
}
static createFromType(type:Type, annotation:Directive):DirectiveBinding {
var binding = new Binding(type, {toClass: type});
return DirectiveBinding.createFromBinding(binding, annotation);
}
}
// TODO(rado): benchmark and consider rolling in as ElementInjector fields.
export class PreBuiltObjects {
view:viewModule.AppView;
element:NgElement;
changeDetector:ChangeDetector;
constructor(view, element:NgElement, changeDetector:ChangeDetector) {
this.view = view;
this.element = element;
this.changeDetector = changeDetector;
}
}
class EventEmitterAccessor {
eventName:string;
getter:Function;
constructor(eventName:string, getter:Function) {
this.eventName = eventName;
this.getter = getter;
}
subscribe(view:viewModule.AppView, boundElementIndex:number, directive:Object) {
var eventEmitter = this.getter(directive);
return ObservableWrapper.subscribe(eventEmitter,
eventObj => view.triggerEventHandlers(this.eventName, eventObj, boundElementIndex));
}
}
/**
Difference between di.Injector and ElementInjector
di.Injector:
- imperative based (can create child injectors imperativly)
- Lazy loading of code
- Component/App Level services which are usually not DOM Related.
ElementInjector:
- ProtoBased (Injector structure fixed at compile time)
- understands @Ancestor, @Parent, @Child, @Descendent
- Fast
- Query mechanism for children
- 1:1 to DOM structure.
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;
_keyId0:int;
_keyId1:int;
_keyId2:int;
_keyId3:int;
_keyId4:int;
_keyId5:int;
_keyId6:int;
_keyId7:int;
_keyId8:int;
_keyId9:int;
parent:ProtoElementInjector;
index:int;
view:viewModule.AppView;
distanceToParent:number;
attributes:Map;
eventEmitterAccessors:List<List<EventEmitterAccessor>>;
numberOfDirectives:number;
/** Whether the element is exported as $implicit. */
exportElement:boolean;
/** Whether the component instance is exported as $implicit. */
exportComponent:boolean;
/** 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) {
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.eventEmitterAccessors = 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);
}
if (length > 1) {
this._binding1 = this._createBinding(bindings[1]);
this._keyId1 = this._binding1.key.id;
this.eventEmitterAccessors[1] = this._createEventEmitterAccessors(this._binding1);
}
if (length > 2) {
this._binding2 = this._createBinding(bindings[2]);
this._keyId2 = this._binding2.key.id;
this.eventEmitterAccessors[2] = this._createEventEmitterAccessors(this._binding2);
}
if (length > 3) {
this._binding3 = this._createBinding(bindings[3]);
this._keyId3 = this._binding3.key.id;
this.eventEmitterAccessors[3] = this._createEventEmitterAccessors(this._binding3);
}
if (length > 4) {
this._binding4 = this._createBinding(bindings[4]);
this._keyId4 = this._binding4.key.id;
this.eventEmitterAccessors[4] = this._createEventEmitterAccessors(this._binding4);
}
if (length > 5) {
this._binding5 = this._createBinding(bindings[5]);
this._keyId5 = this._binding5.key.id;
this.eventEmitterAccessors[5] = this._createEventEmitterAccessors(this._binding5);
}
if (length > 6) {
this._binding6 = this._createBinding(bindings[6]);
this._keyId6 = this._binding6.key.id;
this.eventEmitterAccessors[6] = this._createEventEmitterAccessors(this._binding6);
}
if (length > 7) {
this._binding7 = this._createBinding(bindings[7]);
this._keyId7 = this._binding7.key.id;
this.eventEmitterAccessors[7] = this._createEventEmitterAccessors(this._binding7);
}
if (length > 8) {
this._binding8 = this._createBinding(bindings[8]);
this._keyId8 = this._binding8.key.id;
this.eventEmitterAccessors[8] = this._createEventEmitterAccessors(this._binding8);
}
if (length > 9) {
this._binding9 = this._createBinding(bindings[9]);
this._keyId9 = this._binding9.key.id;
this.eventEmitterAccessors[9] = this._createEventEmitterAccessors(this._binding9);
}
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))
);
}
instantiate(parent:ElementInjector):ElementInjector {
return new ElementInjector(this, parent);
}
directParent(): 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) {
if (index == 0) return this._binding0;
if (index == 1) return this._binding1;
if (index == 2) return this._binding2;
if (index == 3) return this._binding3;
if (index == 4) return this._binding4;
if (index == 5) return this._binding5;
if (index == 6) return this._binding6;
if (index == 7) return this._binding7;
if (index == 8) return this._binding8;
if (index == 9) return this._binding9;
throw new OutOfBoundsAccess(index);
}
}
export class ElementInjector extends TreeNode {
_proto:ProtoElementInjector;
_lightDomAppInjector:Injector;
_shadowDomAppInjector:Injector;
_host:ElementInjector;
// If this element injector has a component, the component instance will be stored in _obj0
_obj0:any;
_obj1:any;
_obj2:any;
_obj3:any;
_obj4:any;
_obj5:any;
_obj6:any;
_obj7:any;
_obj8:any;
_obj9:any;
_preBuiltObjects;
_constructionCounter;
_dynamicallyCreatedComponent:any;
_dynamicallyCreatedComponentBinding:DirectiveBinding;
// Queries are added during construction or linking with a new parent.
// They are never removed.
_query0: QueryRef;
_query1: QueryRef;
_query2: QueryRef;
constructor(proto:ProtoElementInjector, parent:ElementInjector) {
super(parent);
this._proto = proto;
//we cannot call clearDirectives because fields won't be detected
this._preBuiltObjects = null;
this._lightDomAppInjector = null;
this._shadowDomAppInjector = null;
this._obj0 = null;
this._obj1 = null;
this._obj2 = null;
this._obj3 = null;
this._obj4 = null;
this._obj5 = null;
this._obj6 = null;
this._obj7 = null;
this._obj8 = null;
this._obj9 = null;
this._constructionCounter = 0;
this._inheritQueries(parent);
this._buildQueries();
}
clearDirectives() {
this._host = null;
this._preBuiltObjects = null;
this._lightDomAppInjector = null;
this._shadowDomAppInjector = null;
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 (isPresent(this._dynamicallyCreatedComponentBinding) && this._dynamicallyCreatedComponentBinding.callOnDestroy) {
this._dynamicallyCreatedComponent.onDestroy();
}
this._obj0 = null;
this._obj1 = null;
this._obj2 = null;
this._obj3 = null;
this._obj4 = null;
this._obj5 = null;
this._obj6 = null;
this._obj7 = null;
this._obj8 = null;
this._obj9 = null;
this._dynamicallyCreatedComponent = null;
this._dynamicallyCreatedComponentBinding = null;
this._constructionCounter = 0;
}
instantiateDirectives(lightDomAppInjector:Injector, host:ElementInjector, shadowDomAppInjector:Injector, preBuiltObjects:PreBuiltObjects) {
this._host = host;
this._checkShadowDomAppInjector(shadowDomAppInjector);
this._preBuiltObjects = preBuiltObjects;
this._lightDomAppInjector = lightDomAppInjector;
this._shadowDomAppInjector = shadowDomAppInjector;
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);
}
dynamicallyCreateComponent(directiveBinding, injector:Injector) {
this._shadowDomAppInjector = injector;
this._dynamicallyCreatedComponentBinding = directiveBinding;
this._dynamicallyCreatedComponent = this._new(this._dynamicallyCreatedComponentBinding);
return this._dynamicallyCreatedComponent;
}
_checkShadowDomAppInjector(shadowDomAppInjector:Injector) {
if (this._proto._binding0IsComponent && isBlank(shadowDomAppInjector)) {
throw new BaseException('A shadowDomAppInjector is required as this ElementInjector contains a component');
} else if (!this._proto._binding0IsComponent && isPresent(shadowDomAppInjector)) {
throw new BaseException('No shadowDomAppInjector allowed as there is not component stored in this ElementInjector');
}
}
get(token) {
if (this._isDynamicallyLoadedComponent(token)) {
return this._dynamicallyCreatedComponent;
}
return this._getByKey(Key.get(token), 0, false, null);
}
_isDynamicallyLoadedComponent(token) {
return isPresent(this._dynamicallyCreatedComponentBinding) &&
Key.get(token) === this._dynamicallyCreatedComponentBinding.key;
}
hasDirective(type:Type):boolean {
return this._getDirectiveByKeyId(Key.get(type).id) !== _undefined;
}
getEventEmitterAccessors() {
return this._proto.eventEmitterAccessors;
}
/** Gets the NgElement associated with this ElementInjector */
getNgElement() {
return this._preBuiltObjects.element;
}
getChangeDetector() {
return this._preBuiltObjects.changeDetector;
}
getComponent() {
if (this._proto._binding0IsComponent) {
return this._obj0;
} else {
throw new BaseException('There is no component stored in this ElementInjector');
}
}
getElementRef() {
return new ElementRef(this, this._preBuiltObjects.view, this._proto.index, this._lightDomAppInjector);
}
getDynamicallyLoadedComponent() {
return this._dynamicallyCreatedComponent;
}
directParent(): ElementInjector {
return this._proto.distanceToParent < 2 ? this.parent : null;
}
_isComponentKey(key:Key) {
return this._proto._binding0IsComponent && key.id === this._proto._keyId0;
}
_isDynamicallyLoadedComponentKey(key:Key) {
return isPresent(this._dynamicallyCreatedComponentBinding) && key.id ===
this._dynamicallyCreatedComponentBinding.key.id;
}
_new(binding:ResolvedBinding) {
if (this._constructionCounter++ > _MAX_DIRECTIVE_CONSTRUCTION_COUNTER) {
throw new CyclicDependencyError(binding.key);
}
var factory = binding.factory;
var deps = binding.dependencies;
var length = deps.length;
var d0,d1,d2,d3,d4,d5,d6,d7,d8,d9;
try {
d0 = length > 0 ? this._getByDependency(deps[0], binding.key) : null;
d1 = length > 1 ? this._getByDependency(deps[1], binding.key) : null;
d2 = length > 2 ? this._getByDependency(deps[2], binding.key) : null;
d3 = length > 3 ? this._getByDependency(deps[3], binding.key) : null;
d4 = length > 4 ? this._getByDependency(deps[4], binding.key) : null;
d5 = length > 5 ? this._getByDependency(deps[5], binding.key) : null;
d6 = length > 6 ? this._getByDependency(deps[6], binding.key) : null;
d7 = length > 7 ? this._getByDependency(deps[7], binding.key) : null;
d8 = length > 8 ? this._getByDependency(deps[8], binding.key) : null;
d9 = length > 9 ? this._getByDependency(deps[9], binding.key) : null;
} catch(e) {
if (e instanceof AbstractBindingError) e.addKey(binding.key);
throw e;
}
var obj;
switch(length) {
case 0: obj = factory(); break;
case 1: obj = factory(d0); break;
case 2: obj = factory(d0, d1); break;
case 3: obj = factory(d0, d1, d2); break;
case 4: obj = factory(d0, d1, d2, d3); break;
case 5: obj = factory(d0, d1, d2, d3, d4); break;
case 6: obj = factory(d0, d1, d2, d3, d4, d5); break;
case 7: obj = factory(d0, d1, d2, d3, d4, d5, d6); break;
case 8: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7); break;
case 9: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8); break;
case 10: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9); break;
default: throw `Directive ${binding.key.token} can only have up to 10 dependencies.`;
}
this._addToQueries(obj, binding.key.token);
return obj;
}
_getByDependency(dep:DirectiveDependency, requestor:Key) {
if (isPresent(dep.propSetterName)) return this._buildPropSetter(dep);
if (isPresent(dep.attributeName)) return this._buildAttribute(dep);
if (isPresent(dep.queryDirective)) return this._findQuery(dep.queryDirective).list;
if (dep.key.id === StaticKeys.instance().elementRefId) {
return this.getElementRef();
}
return this._getByKey(dep.key, dep.depth, dep.optional, requestor);
}
_buildPropSetter(dep) {
var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId);
var renderer = view.renderer;
var index = this._proto.index;
return function(v) {
renderer.setElementProperty(view.render, index, dep.propSetterName, v);
};
}
_buildAttribute(dep): string {
var attributes = this._proto.attributes;
if (isPresent(attributes) && MapWrapper.contains(attributes, dep.attributeName)) {
return MapWrapper.get(attributes, dep.attributeName);
} else {
return null;
}
}
_buildQueriesForDeps(deps: List<DirectiveDependency>) {
for (var i = 0; i < deps.length; i++) {
var dep = deps[i];
if (isPresent(dep.queryDirective)) {
this._createQueryRef(dep.queryDirective);
}
}
}
_createQueryRef(directive) {
var queryList = new QueryList();
if (isBlank(this._query0)) {this._query0 = new QueryRef(directive, queryList, this);}
else if (isBlank(this._query1)) {this._query1 = new QueryRef(directive, queryList, this);}
else if (isBlank(this._query2)) {this._query2 = new QueryRef(directive, queryList, this);}
else throw new QueryError();
}
_addToQueries(obj, token) {
if (isPresent(this._query0) && (this._query0.directive === token)) {this._query0.list.add(obj);}
if (isPresent(this._query1) && (this._query1.directive === token)) {this._query1.list.add(obj);}
if (isPresent(this._query2) && (this._query2.directive === token)) {this._query2.list.add(obj);}
}
// TODO(rado): unify with _addParentQueries.
_inheritQueries(parent: ElementInjector) {
if (isBlank(parent)) return;
if (isPresent(parent._query0)) {this._query0 = parent._query0;}
if (isPresent(parent._query1)) {this._query1 = parent._query1;}
if (isPresent(parent._query2)) {this._query2 = parent._query2;}
}
_buildQueries() {
if (isBlank(this._proto)) return;
var p = this._proto;
if (isPresent(p._binding0)) {this._buildQueriesForDeps(p._binding0.dependencies);}
if (isPresent(p._binding1)) {this._buildQueriesForDeps(p._binding1.dependencies);}
if (isPresent(p._binding2)) {this._buildQueriesForDeps(p._binding2.dependencies);}
if (isPresent(p._binding3)) {this._buildQueriesForDeps(p._binding3.dependencies);}
if (isPresent(p._binding4)) {this._buildQueriesForDeps(p._binding4.dependencies);}
if (isPresent(p._binding5)) {this._buildQueriesForDeps(p._binding5.dependencies);}
if (isPresent(p._binding6)) {this._buildQueriesForDeps(p._binding6.dependencies);}
if (isPresent(p._binding7)) {this._buildQueriesForDeps(p._binding7.dependencies);}
if (isPresent(p._binding8)) {this._buildQueriesForDeps(p._binding8.dependencies);}
if (isPresent(p._binding9)) {this._buildQueriesForDeps(p._binding9.dependencies);}
}
_findQuery(token) {
if (isPresent(this._query0) && this._query0.directive === token) {return this._query0;}
if (isPresent(this._query1) && this._query1.directive === token) {return this._query1;}
if (isPresent(this._query2) && this._query2.directive === token) {return this._query2;}
throw new BaseException(`Cannot find query for directive ${token}.`);
}
link(parent: ElementInjector) {
parent.addChild(this);
this._addParentQueries();
}
linkAfter(parent: ElementInjector, prevSibling: ElementInjector) {
parent.addChildAfter(this, prevSibling);
this._addParentQueries();
}
_addParentQueries() {
if (isPresent(this.parent._query0)) {this._addQueryToTree(this.parent._query0); this.parent._query0.update();}
if (isPresent(this.parent._query1)) {this._addQueryToTree(this.parent._query1); this.parent._query1.update();}
if (isPresent(this.parent._query2)) {this._addQueryToTree(this.parent._query2); this.parent._query2.update();}
}
unlink() {
var queriesToUpDate = [];
if (isPresent(this.parent._query0)) {this._pruneQueryFromTree(this.parent._query0); ListWrapper.push(queriesToUpDate, this.parent._query0);}
if (isPresent(this.parent._query1)) {this._pruneQueryFromTree(this.parent._query1); ListWrapper.push(queriesToUpDate, this.parent._query1);}
if (isPresent(this.parent._query2)) {this._pruneQueryFromTree(this.parent._query2); ListWrapper.push(queriesToUpDate, this.parent._query2);}
this.remove();
ListWrapper.forEach(queriesToUpDate, (q) => q.update());
}
_pruneQueryFromTree(query: QueryRef) {
this._removeQueryRef(query);
var child = this._head;
while (isPresent(child)) {
child._pruneQueryFromTree(query);
child = child._next;
}
}
_addQueryToTree(query: QueryRef) {
this._assignQueryRef(query);
var child = this._head;
while (isPresent(child)) {
child._addQueryToTree(query);
child = child._next;
}
}
_assignQueryRef(query: QueryRef) {
if (isBlank(this._query0)) {this._query0 = query; return;}
else if (isBlank(this._query1)) {this._query1 = query; return;}
else if (isBlank(this._query2)) {this._query2 = query; return;}
throw new QueryError();
}
_removeQueryRef(query: QueryRef) {
if (this._query0 == query) this._query0 = null;
if (this._query1 == query) this._query1 = null;
if (this._query2 == query) this._query2 = null;
}
/*
* It is fairly easy to annotate keys with metadata.
* For example, key.metadata = 'directive'.
*
* This would allows to do the lookup more efficiently.
*
* for example
* we would lookup pre built objects only when metadata = 'preBuilt'
* we would lookup directives only when metadata = 'directive'
*
* Write benchmarks before doing this optimization.
*/
_getByKey(key:Key, depth:number, optional:boolean, requestor:Key) {
var ei = this;
if (! this._shouldIncludeSelf(depth)) {
depth -= ei._proto.distanceToParent;
ei = ei._parent;
}
while (ei != null && depth >= 0) {
var preBuiltObj = ei._getPreBuiltObjectByKeyId(key.id);
if (preBuiltObj !== _undefined) return preBuiltObj;
var dir = ei._getDirectiveByKeyId(key.id);
if (dir !== _undefined) return dir;
depth -= ei._proto.distanceToParent;
ei = ei._parent;
}
if (isPresent(this._host) && this._host._isComponentKey(key)) {
return this._host.getComponent();
} else if (isPresent(this._host) && this._host._isDynamicallyLoadedComponentKey(key)) {
return this._host.getDynamicallyLoadedComponent();
} else if (optional) {
return this._appInjector(requestor).getOptional(key);
} else {
return this._appInjector(requestor).get(key);
}
}
_appInjector(requestor:Key) {
if (isPresent(requestor) && (this._isComponentKey(requestor) || this._isDynamicallyLoadedComponentKey(requestor))) {
return this._shadowDomAppInjector;
} else {
return this._lightDomAppInjector;
}
}
_shouldIncludeSelf(depth:int) {
return depth === 0;
}
_getPreBuiltObjectByKeyId(keyId:int) {
var staticKeys = StaticKeys.instance();
// TODO: AppView should not be injectable. Remove it.
if (keyId === staticKeys.viewId) return this._preBuiltObjects.view;
if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element;
if (keyId === staticKeys.viewContainerId) return this._preBuiltObjects.view.getOrCreateViewContainer(this._proto.index);
if (keyId === staticKeys.changeDetectorRefId) return this._preBuiltObjects.changeDetector.ref;
//TODO add other objects as needed
return _undefined;
}
_getDirectiveByKeyId(keyId:int) {
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;}
return _undefined;
}
getDirectiveAtIndex(index:int) {
if (index == 0) return this._obj0;
if (index == 1) return this._obj1;
if (index == 2) return this._obj2;
if (index == 3) return this._obj3;
if (index == 4) return this._obj4;
if (index == 5) return this._obj5;
if (index == 6) return this._obj6;
if (index == 7) return this._obj7;
if (index == 8) return this._obj8;
if (index == 9) return this._obj9;
throw new OutOfBoundsAccess(index);
}
hasInstances() {
return this._constructionCounter > 0;
}
/** Gets whether this element is exporting a component instance as $implicit. */
isExportingComponent() {
return this._proto.exportComponent;
}
/** Gets whether this element is exporting its element as $implicit. */
isExportingElement() {
return this._proto.exportElement;
}
/** Get the name to which this element's $implicit is to be assigned. */
getExportImplicitName() {
return this._proto.exportImplicitName;
}
getLightDomAppInjector() {
return this._lightDomAppInjector;
}
getHost() {
return this._host;
}
getBoundElementIndex() {
return this._proto.index;
}
}
class OutOfBoundsAccess extends Error {
message:string;
constructor(index) {
super();
this.message = `Index ${index} is out-of-bounds.`;
}
toString() {
return this.message;
}
}
class QueryError extends Error {
message:string;
// TODO(rado): pass the names of the active directives.
constructor() {
super();
this.message = 'Only 3 queries can be concurrently active in a template.';
}
toString() {
return this.message;
}
}
class QueryRef {
directive;
list: QueryList;
originator: ElementInjector;
constructor(directive, list: QueryList, originator: ElementInjector) {
this.directive = directive;
this.list = list;
this.originator = originator;
}
update() {
var aggregator = [];
this.visit(this.originator, aggregator);
this.list.reset(aggregator);
}
visit(inj: ElementInjector, aggregator) {
if (isBlank(inj)) return;
if (inj.hasDirective(this.directive)) {
ListWrapper.push(aggregator, inj.get(this.directive));
}
var child = inj._head;
while (isPresent(child)) {
this.visit(child, aggregator);
child = child._next;
}
}
}