feat(ElementInjector): add NgElement

This commit is contained in:
vsavkin 2014-11-14 10:58:58 -08:00
parent ac8351b7bc
commit d7208b8429
8 changed files with 106 additions and 107 deletions

View File

@ -10,8 +10,8 @@ export function run () {
var bindings = [A, B, C]; var bindings = [A, B, C];
var proto = new ProtoElementInjector(null, 0, bindings); var proto = new ProtoElementInjector(null, 0, bindings);
for (var i = 0; i < ITERATIONS; ++i) { for (var i = 0; i < ITERATIONS; ++i) {
var ei = proto.instantiate(null,null,null); var ei = proto.instantiate(null, null);
ei.instantiateDirectives(appInjector, null); ei.instantiateDirectives(appInjector, null, null);
} }
} }

View File

@ -18,8 +18,8 @@ export function run () {
var proto = new ProtoElementInjector(null, 0, bindings); var proto = new ProtoElementInjector(null, 0, bindings);
for (var i = 0; i < ITERATIONS; ++i) { for (var i = 0; i < ITERATIONS; ++i) {
var ei = proto.instantiate(null,null,null); var ei = proto.instantiate(null,null);
ei.instantiateDirectives(appInjector, null); ei.instantiateDirectives(appInjector, null, null);
} }
} }

View File

@ -9,11 +9,11 @@ export function run () {
var bindings = [A, B, C]; var bindings = [A, B, C];
var proto = new ProtoElementInjector(null, 0, bindings); var proto = new ProtoElementInjector(null, 0, bindings);
var ei = proto.instantiate(null,null,null); var ei = proto.instantiate(null,null);
for (var i = 0; i < ITERATIONS; ++i) { for (var i = 0; i < ITERATIONS; ++i) {
ei.clearDirectives(); ei.clearDirectives();
ei.instantiateDirectives(appInjector, null); ei.instantiateDirectives(appInjector, null, null);
} }
} }

View File

@ -6,6 +6,7 @@ import {Parent, Ancestor} from 'core/annotations/visibility';
import {StaticKeys} from './static_keys'; import {StaticKeys} from './static_keys';
// Comment out as dartanalyzer does not look into @FIELD // Comment out as dartanalyzer does not look into @FIELD
// import {View} from './view'; // import {View} from './view';
import {NgElement} from 'core/dom/element';
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10; var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;
@ -75,6 +76,15 @@ class DirectiveDependency extends Dependency {
} }
} }
export class PreBuiltObjects {
@FIELD('final view:View')
@FIELD('final element:NgElement')
constructor(view, element:NgElement) {
this.view = view;
this.element = element;
}
}
/** /**
Difference between di.Injector and ElementInjector Difference between di.Injector and ElementInjector
@ -152,8 +162,8 @@ export class ProtoElementInjector {
} }
} }
instantiate(parent:ElementInjector, host:ElementInjector, view):ElementInjector { instantiate(parent:ElementInjector, host:ElementInjector):ElementInjector {
return new ElementInjector(this, parent, host, view); return new ElementInjector(this, parent, host);
} }
_createBinding(bindingOrType) { _createBinding(bindingOrType) {
@ -170,49 +180,6 @@ export class ProtoElementInjector {
} }
export class ElementInjector extends TreeNode { export class ElementInjector extends TreeNode {
/*
_protoInjector:ProtoElementInjector;
injector:Injector;
_parent:ElementInjector;
_next:ElementInjector;
_prev:ElementInjector;
_head:ElementInjector;
_tail:ElementInjector;
// For performance reasons the Injector only supports 10 directives per element.
// NOTE: linear search over fields is faster than HashMap lookup.
_cObj; // Component only
_obj0;
_obj1;
_obj2;
_obj3;
_obj4;
_obj5;
_obj6;
_obj7;
_obj8;
_obj9;
element:Element;
ngElement:NgElement;
shadowRoot:ShadowRoot;
elementProbe:ElementProbe;
view:View;
viewPort:ViewPort;
viewFactory:ViewFactory;
animate:Animate;
destinationLightDom:DestinationLightDom;
sourceLightDom:SourceLightDom;
// For performance reasons the Injector only supports 2 [Query] per element.
// NOTE: linear search over fields is faster than HashMap lookup.
_query0:Query;
_query1:Query;
*/
@FIELD('_proto:ProtoElementInjector') @FIELD('_proto:ProtoElementInjector')
@FIELD('_lightDomAppInjector:Injector') @FIELD('_lightDomAppInjector:Injector')
@FIELD('_shadowDomAppInjector:Injector') @FIELD('_shadowDomAppInjector:Injector')
@ -228,7 +195,7 @@ export class ElementInjector extends TreeNode {
@FIELD('_obj8:Object') @FIELD('_obj8:Object')
@FIELD('_obj9:Object') @FIELD('_obj9:Object')
@FIELD('_view:View') @FIELD('_view:View')
constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector, view) { constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector) {
super(parent); super(parent);
if (isPresent(parent) && isPresent(host)) { if (isPresent(parent) && isPresent(host)) {
throw new BaseException('Only either parent or host is allowed'); throw new BaseException('Only either parent or host is allowed');
@ -241,9 +208,9 @@ export class ElementInjector extends TreeNode {
} }
this._proto = proto; this._proto = proto;
this._view = view;
//we cannot call clearDirectives because fields won't be detected //we cannot call clearDirectives because fields won't be detected
this._preBuiltObjects = null;
this._lightDomAppInjector = null; this._lightDomAppInjector = null;
this._shadowDomAppInjector = null; this._shadowDomAppInjector = null;
this._obj0 = null; this._obj0 = null;
@ -260,6 +227,7 @@ export class ElementInjector extends TreeNode {
} }
clearDirectives() { clearDirectives() {
this._preBuiltObjects = null;
this._lightDomAppInjector = null; this._lightDomAppInjector = null;
this._shadowDomAppInjector = null; this._shadowDomAppInjector = null;
@ -276,16 +244,14 @@ export class ElementInjector extends TreeNode {
this._constructionCounter = 0; this._constructionCounter = 0;
} }
instantiateDirectives(lightDomAppInjector:Injector, shadowDomAppInjector:Injector) { instantiateDirectives(lightDomAppInjector:Injector, shadowDomAppInjector:Injector, preBuiltObjects:PreBuiltObjects) {
var p = this._proto; this._checkShadowDomAppInjector(shadowDomAppInjector);
if (this._proto._binding0IsComponent && isBlank(shadowDomAppInjector)) {
throw new BaseException('A shadowDomAppInjector is required as this ElementInjector contains a component'); this._preBuiltObjects = preBuiltObjects;
} else if (!this._proto._binding0IsComponent && isPresent(shadowDomAppInjector)) {
throw new BaseException('No shadowDomAppInjector allowed as there is not component stored in this ElementInjector');
}
this._lightDomAppInjector = lightDomAppInjector; this._lightDomAppInjector = lightDomAppInjector;
this._shadowDomAppInjector = shadowDomAppInjector; this._shadowDomAppInjector = shadowDomAppInjector;
var p = this._proto;
if (isPresent(p._keyId0)) this._getDirectiveByKeyId(p._keyId0); if (isPresent(p._keyId0)) this._getDirectiveByKeyId(p._keyId0);
if (isPresent(p._keyId1)) this._getDirectiveByKeyId(p._keyId1); if (isPresent(p._keyId1)) this._getDirectiveByKeyId(p._keyId1);
if (isPresent(p._keyId2)) this._getDirectiveByKeyId(p._keyId2); if (isPresent(p._keyId2)) this._getDirectiveByKeyId(p._keyId2);
@ -298,6 +264,14 @@ export class ElementInjector extends TreeNode {
if (isPresent(p._keyId9)) this._getDirectiveByKeyId(p._keyId9); if (isPresent(p._keyId9)) this._getDirectiveByKeyId(p._keyId9);
} }
_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) { get(token) {
return this._getByKey(Key.get(token), 0, null); return this._getByKey(Key.get(token), 0, null);
} }
@ -370,7 +344,7 @@ export class ElementInjector extends TreeNode {
* This would allows to do the lookup more efficiently. * This would allows to do the lookup more efficiently.
* *
* for example * for example
* we would lookup special objects only when metadata = 'special' * we would lookup pre built objects only when metadata = 'preBuilt'
* we would lookup directives only when metadata = 'directive' * we would lookup directives only when metadata = 'directive'
* *
* Write benchmarks before doing this optimization. * Write benchmarks before doing this optimization.
@ -384,8 +358,8 @@ export class ElementInjector extends TreeNode {
} }
while (ei != null && depth >= 0) { while (ei != null && depth >= 0) {
var specObj = ei._getSpecialObjectByKeyId(key.id); var preBuiltObj = ei._getPreBuiltObjectByKeyId(key.id);
if (specObj !== _undefined) return specObj; if (preBuiltObj !== _undefined) return preBuiltObj;
var dir = ei._getDirectiveByKeyId(key.id); var dir = ei._getDirectiveByKeyId(key.id);
if (dir !== _undefined) return dir; if (dir !== _undefined) return dir;
@ -397,13 +371,15 @@ export class ElementInjector extends TreeNode {
if (isPresent(this._host) && this._host._isComponentKey(key)) { if (isPresent(this._host) && this._host._isComponentKey(key)) {
return this._host.getComponent(); return this._host.getComponent();
} else { } else {
var appInjector; return this._appInjector(requestor).get(key);
if (isPresent(requestor) && this._isComponentKey(requestor)) { }
appInjector = this._shadowDomAppInjector; }
} else {
appInjector = this._lightDomAppInjector; _appInjector(requestor:Key) {
} if (isPresent(requestor) && this._isComponentKey(requestor)) {
return appInjector.get(key); return this._shadowDomAppInjector;
} else {
return this._lightDomAppInjector;
} }
} }
@ -411,9 +387,10 @@ export class ElementInjector extends TreeNode {
return depth === 0; return depth === 0;
} }
_getSpecialObjectByKeyId(keyId:int) { _getPreBuiltObjectByKeyId(keyId:int) {
var staticKeys = StaticKeys.instance(); var staticKeys = StaticKeys.instance();
if (keyId === staticKeys.viewId) return this._view; if (keyId === staticKeys.viewId) return this._preBuiltObjects.view;
if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element;
//TODO add other objects as needed //TODO add other objects as needed
return _undefined; return _undefined;
} }

View File

@ -1,4 +1,5 @@
import {View} from 'core/compiler/view'; import {View} from 'core/compiler/view';
import {NgElement} from 'core/dom/element';
import {Key} from 'di/di'; import {Key} from 'di/di';
import {isBlank} from 'facade/lang'; import {isBlank} from 'facade/lang';
@ -8,6 +9,7 @@ export class StaticKeys {
constructor() { constructor() {
//TODO: vsavkin Key.annotate(Key.get(View), 'static') //TODO: vsavkin Key.annotate(Key.get(View), 'static')
this.viewId = Key.get(View).id; this.viewId = Key.get(View).id;
this.ngElementId = Key.get(NgElement).id;
} }
static instance() { static instance() {

View File

@ -4,13 +4,14 @@ import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detectio
import {Record} from 'change_detection/record'; import {Record} from 'change_detection/record';
import {AST} from 'change_detection/parser/ast'; import {AST} from 'change_detection/parser/ast';
import {ProtoElementInjector, ElementInjector} from './element_injector'; import {ProtoElementInjector, ElementInjector, PreBuiltObjects} from './element_injector';
import {ElementBinder} from './element_binder'; import {ElementBinder} from './element_binder';
import {AnnotatedType} from './annotated_type'; import {AnnotatedType} from './annotated_type';
import {SetterFn} from 'change_detection/parser/closure_map'; import {SetterFn} from 'change_detection/parser/closure_map';
import {FIELD, IMPLEMENTS, int, isPresent, isBlank} from 'facade/lang'; import {FIELD, IMPLEMENTS, int, isPresent, isBlank} from 'facade/lang';
import {List} from 'facade/collection'; import {List} from 'facade/collection';
import {Injector} from 'di/di'; import {Injector} from 'di/di';
import {NgElement} from 'core/dom/element';
const NG_BINDING_CLASS = 'ng-binding'; const NG_BINDING_CLASS = 'ng-binding';
@ -94,7 +95,6 @@ export class ProtoView {
var rootElementInjectors = ProtoView._rootElementInjectors(elementInjectors); var rootElementInjectors = ProtoView._rootElementInjectors(elementInjectors);
var textNodes = ProtoView._textNodes(elements, binders); var textNodes = ProtoView._textNodes(elements, binders);
var bindElements = ProtoView._bindElements(elements, binders); var bindElements = ProtoView._bindElements(elements, binders);
ProtoView._instantiateDirectives(elementInjectors, appInjector);
var viewNodes; var viewNodes;
if (clone instanceof TemplateElement) { if (clone instanceof TemplateElement) {
@ -102,8 +102,12 @@ export class ProtoView {
} else { } else {
viewNodes = [clone]; viewNodes = [clone];
} }
return new View(viewNodes, elementInjectors, rootElementInjectors, textNodes, var view = new View(viewNodes, elementInjectors, rootElementInjectors, textNodes,
bindElements, this.protoWatchGroup, context); bindElements, this.protoWatchGroup, context);
ProtoView._instantiateDirectives(view, elements, elementInjectors, appInjector);
return view;
} }
bindElement(protoElementInjector:ProtoElementInjector, bindElement(protoElementInjector:ProtoElementInjector,
@ -173,15 +177,15 @@ export class ProtoView {
} }
static _instantiateDirectives( static _instantiateDirectives(
injectors:List<ElementInjectors>, appInjector:Injector) { view: View, elements:List, injectors:List<ElementInjectors>, appInjector:Injector) {
for (var i = 0; i < injectors.length; ++i) { for (var i = 0; i < injectors.length; ++i) {
if (injectors[i] != null) injectors[i].instantiateDirectives(appInjector, null); var preBuiltObjs = new PreBuiltObjects(view, new NgElement(elements[i]));
if (injectors[i] != null) injectors[i].instantiateDirectives(appInjector, null, preBuiltObjs);
} }
} }
static _createElementInjector(element, parent:ElementInjector, proto:ProtoElementInjector) { static _createElementInjector(element, parent:ElementInjector, proto:ProtoElementInjector) {
//TODO: vsavkin: pass element to `proto.instantiate()` once https://github.com/angular/angular/pull/98 is merged return proto.instantiate(parent, null);
return proto.instantiate(parent, null, null);
} }
static _rootElementInjectors(injectors) { static _rootElementInjectors(injectors) {

View File

@ -0,0 +1,5 @@
export class NgElement {
constructor(domElement) {
this.domElement = domElement;
}
}

View File

@ -1,10 +1,11 @@
import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach} from 'test_lib/test_lib'; import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach} from 'test_lib/test_lib';
import {isBlank, isPresent, FIELD, IMPLEMENTS} from 'facade/lang'; import {isBlank, isPresent, FIELD, IMPLEMENTS} from 'facade/lang';
import {ListWrapper, MapWrapper, List} from 'facade/collection'; import {ListWrapper, MapWrapper, List} from 'facade/collection';
import {ProtoElementInjector, VIEW_KEY} from 'core/compiler/element_injector'; import {ProtoElementInjector, PreBuiltObjects} from 'core/compiler/element_injector';
import {Parent, Ancestor} from 'core/annotations/visibility'; import {Parent, Ancestor} from 'core/annotations/visibility';
import {Injector, Inject, bind} from 'di/di'; import {Injector, Inject, bind} from 'di/di';
import {View} from 'core/compiler/view'; import {View} from 'core/compiler/view';
import {NgElement} from 'core/dom/element';
@IMPLEMENTS(View) @IMPLEMENTS(View)
class DummyView {} class DummyView {}
@ -60,6 +61,8 @@ class NeedsView {
} }
export function main() { export function main() {
var defaultPreBuiltObjects = new PreBuiltObjects(null, null);
function humanize(tree, names:List) { function humanize(tree, names:List) {
var lookupName = (item) => var lookupName = (item) =>
ListWrapper.last( ListWrapper.last(
@ -70,13 +73,14 @@ export function main() {
return [lookupName(tree), children]; return [lookupName(tree), children];
} }
function injector(bindings, lightDomAppInjector = null, shadowDomAppInjector = null, props = null) { function injector(bindings, lightDomAppInjector = null, shadowDomAppInjector = null, preBuiltObjects = null) {
if (isBlank(lightDomAppInjector)) lightDomAppInjector = new Injector([]); if (isBlank(lightDomAppInjector)) lightDomAppInjector = new Injector([]);
if (isBlank(props)) props = {"view": null};
var proto = new ProtoElementInjector(null, 0, bindings, isPresent(shadowDomAppInjector)); var proto = new ProtoElementInjector(null, 0, bindings, isPresent(shadowDomAppInjector));
var inj = proto.instantiate(null, null, props["view"]); var inj = proto.instantiate(null, null);
inj.instantiateDirectives(lightDomAppInjector, shadowDomAppInjector); var preBuilt = isPresent(preBuiltObjects) ? preBuiltObjects : defaultPreBuiltObjects;
inj.instantiateDirectives(lightDomAppInjector, shadowDomAppInjector, preBuilt);
return inj; return inj;
} }
@ -84,12 +88,12 @@ export function main() {
var inj = new Injector([]); var inj = new Injector([]);
var protoParent = new ProtoElementInjector(null, 0, parentBindings); var protoParent = new ProtoElementInjector(null, 0, parentBindings);
var parent = protoParent.instantiate(null, null, null); var parent = protoParent.instantiate(null, null);
parent.instantiateDirectives(inj, null); parent.instantiateDirectives(inj, null, defaultPreBuiltObjects);
var protoChild = new ProtoElementInjector(protoParent, 1, childBindings); var protoChild = new ProtoElementInjector(protoParent, 1, childBindings);
var child = protoChild.instantiate(parent, null, null); var child = protoChild.instantiate(parent, null);
child.instantiateDirectives(inj, null); child.instantiateDirectives(inj, null, defaultPreBuiltObjects);
return child; return child;
} }
@ -99,12 +103,12 @@ export function main() {
var shadowInj = inj.createChild([]); var shadowInj = inj.createChild([]);
var protoParent = new ProtoElementInjector(null, 0, hostBindings, true); var protoParent = new ProtoElementInjector(null, 0, hostBindings, true);
var host = protoParent.instantiate(null, null, null); var host = protoParent.instantiate(null, null);
host.instantiateDirectives(inj, shadowInj); host.instantiateDirectives(inj, shadowInj, null);
var protoChild = new ProtoElementInjector(protoParent, 0, shadowBindings, false); var protoChild = new ProtoElementInjector(protoParent, 0, shadowBindings, false);
var shadow = protoChild.instantiate(null, host, null); var shadow = protoChild.instantiate(null, host);
shadow.instantiateDirectives(shadowInj, null); shadow.instantiateDirectives(shadowInj, null, null);
return shadow; return shadow;
} }
@ -116,9 +120,9 @@ export function main() {
var protoChild1 = new ProtoElementInjector(protoParent, 1, []); var protoChild1 = new ProtoElementInjector(protoParent, 1, []);
var protoChild2 = new ProtoElementInjector(protoParent, 2, []); var protoChild2 = new ProtoElementInjector(protoParent, 2, []);
var p = protoParent.instantiate(null, null, null); var p = protoParent.instantiate(null, null);
var c1 = protoChild1.instantiate(p, null, null); var c1 = protoChild1.instantiate(p, null);
var c2 = protoChild2.instantiate(p, null, null); var c2 = protoChild2.instantiate(p, null);
expect(humanize(p, [ expect(humanize(p, [
[p, 'parent'], [p, 'parent'],
@ -166,9 +170,9 @@ export function main() {
expect(d.service).toEqual("service"); expect(d.service).toEqual("service");
}); });
it("should instantiate directives that depend on special objects", function () { it("should instantiate directives that depend on pre built objects", function () {
var view = new DummyView(); var view = new DummyView();
var inj = injector([NeedsView], null, null, {'view':view}); var inj = injector([NeedsView], null, null, new PreBuiltObjects(view, null));
expect(inj.get(NeedsView).view).toBe(view); expect(inj.get(NeedsView).view).toBe(view);
}); });
@ -258,15 +262,6 @@ export function main() {
expect(() => inj.getAtIndex(10)).toThrowError( expect(() => inj.getAtIndex(10)).toThrowError(
'Index 10 is out-of-bounds.'); 'Index 10 is out-of-bounds.');
}); });
});
describe("special objects", function () {
it("should return view", function () {
var view = new DummyView();
var inj = injector([], null, null, {"view" : view});
expect(inj.get(View)).toEqual(view);
});
it("should handle cyclic dependencies", function () { it("should handle cyclic dependencies", function () {
expect(() => { expect(() => {
@ -275,7 +270,23 @@ export function main() {
bind(B_Needs_A).toFactory((a) => new B_Needs_A(a), [A_Needs_B]) bind(B_Needs_A).toFactory((a) => new B_Needs_A(a), [A_Needs_B])
]); ]);
}).toThrowError('Cannot instantiate cyclic dependency! ' + }).toThrowError('Cannot instantiate cyclic dependency! ' +
'(A_Needs_B -> B_Needs_A -> A_Needs_B)'); '(A_Needs_B -> B_Needs_A -> A_Needs_B)');
});
});
describe("pre built objects", function () {
it("should return view", function () {
var view = new DummyView();
var inj = injector([], null, null, new PreBuiltObjects(view, null));
expect(inj.get(View)).toEqual(view);
});
it("should return element", function () {
var element = new NgElement(null);
var inj = injector([], null, null, new PreBuiltObjects(null, element));
expect(inj.get(NgElement)).toEqual(element);
}); });
}); });
}); });