From d7208b842960fd04db15feba994d439d6955251d Mon Sep 17 00:00:00 2001 From: vsavkin Date: Fri, 14 Nov 2014 10:58:58 -0800 Subject: [PATCH] feat(ElementInjector): add NgElement --- .../element_injector/instantiate_benchmark.js | 4 +- .../instantiate_benchmark_codegen.js | 4 +- .../instantiate_directive_benchmark.js | 4 +- modules/core/src/compiler/element_injector.js | 109 +++++++----------- modules/core/src/compiler/static_keys.js | 2 + modules/core/src/compiler/view.js | 18 +-- modules/core/src/dom/element.js | 5 + .../test/compiler/element_injector_spec.js | 67 ++++++----- 8 files changed, 106 insertions(+), 107 deletions(-) create mode 100644 modules/core/src/dom/element.js diff --git a/modules/benchmarks/src/element_injector/instantiate_benchmark.js b/modules/benchmarks/src/element_injector/instantiate_benchmark.js index ea5f212262..c034986e9c 100644 --- a/modules/benchmarks/src/element_injector/instantiate_benchmark.js +++ b/modules/benchmarks/src/element_injector/instantiate_benchmark.js @@ -10,8 +10,8 @@ export function run () { var bindings = [A, B, C]; var proto = new ProtoElementInjector(null, 0, bindings); for (var i = 0; i < ITERATIONS; ++i) { - var ei = proto.instantiate(null,null,null); - ei.instantiateDirectives(appInjector, null); + var ei = proto.instantiate(null, null); + ei.instantiateDirectives(appInjector, null, null); } } diff --git a/modules/benchmarks/src/element_injector/instantiate_benchmark_codegen.js b/modules/benchmarks/src/element_injector/instantiate_benchmark_codegen.js index 4f50a2af8b..c92fbc092f 100644 --- a/modules/benchmarks/src/element_injector/instantiate_benchmark_codegen.js +++ b/modules/benchmarks/src/element_injector/instantiate_benchmark_codegen.js @@ -18,8 +18,8 @@ export function run () { var proto = new ProtoElementInjector(null, 0, bindings); for (var i = 0; i < ITERATIONS; ++i) { - var ei = proto.instantiate(null,null,null); - ei.instantiateDirectives(appInjector, null); + var ei = proto.instantiate(null,null); + ei.instantiateDirectives(appInjector, null, null); } } diff --git a/modules/benchmarks/src/element_injector/instantiate_directive_benchmark.js b/modules/benchmarks/src/element_injector/instantiate_directive_benchmark.js index 99057ba2a5..e9d3b06b99 100644 --- a/modules/benchmarks/src/element_injector/instantiate_directive_benchmark.js +++ b/modules/benchmarks/src/element_injector/instantiate_directive_benchmark.js @@ -9,11 +9,11 @@ export function run () { var bindings = [A, B, C]; 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) { ei.clearDirectives(); - ei.instantiateDirectives(appInjector, null); + ei.instantiateDirectives(appInjector, null, null); } } diff --git a/modules/core/src/compiler/element_injector.js b/modules/core/src/compiler/element_injector.js index 43978bf094..55a645999e 100644 --- a/modules/core/src/compiler/element_injector.js +++ b/modules/core/src/compiler/element_injector.js @@ -6,6 +6,7 @@ import {Parent, Ancestor} from 'core/annotations/visibility'; import {StaticKeys} from './static_keys'; // Comment out as dartanalyzer does not look into @FIELD // import {View} from './view'; +import {NgElement} from 'core/dom/element'; 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 @@ -152,8 +162,8 @@ export class ProtoElementInjector { } } - instantiate(parent:ElementInjector, host:ElementInjector, view):ElementInjector { - return new ElementInjector(this, parent, host, view); + instantiate(parent:ElementInjector, host:ElementInjector):ElementInjector { + return new ElementInjector(this, parent, host); } _createBinding(bindingOrType) { @@ -170,49 +180,6 @@ export class ProtoElementInjector { } 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('_lightDomAppInjector:Injector') @FIELD('_shadowDomAppInjector:Injector') @@ -228,7 +195,7 @@ export class ElementInjector extends TreeNode { @FIELD('_obj8:Object') @FIELD('_obj9:Object') @FIELD('_view:View') - constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector, view) { + constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector) { super(parent); if (isPresent(parent) && isPresent(host)) { throw new BaseException('Only either parent or host is allowed'); @@ -241,9 +208,9 @@ export class ElementInjector extends TreeNode { } this._proto = proto; - this._view = view; //we cannot call clearDirectives because fields won't be detected + this._preBuiltObjects = null; this._lightDomAppInjector = null; this._shadowDomAppInjector = null; this._obj0 = null; @@ -260,6 +227,7 @@ export class ElementInjector extends TreeNode { } clearDirectives() { + this._preBuiltObjects = null; this._lightDomAppInjector = null; this._shadowDomAppInjector = null; @@ -276,16 +244,14 @@ export class ElementInjector extends TreeNode { this._constructionCounter = 0; } - instantiateDirectives(lightDomAppInjector:Injector, shadowDomAppInjector:Injector) { - var p = this._proto; - 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'); - } + instantiateDirectives(lightDomAppInjector:Injector, shadowDomAppInjector:Injector, preBuiltObjects:PreBuiltObjects) { + 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); @@ -298,6 +264,14 @@ export class ElementInjector extends TreeNode { 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) { 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. * * 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' * * Write benchmarks before doing this optimization. @@ -384,8 +358,8 @@ export class ElementInjector extends TreeNode { } while (ei != null && depth >= 0) { - var specObj = ei._getSpecialObjectByKeyId(key.id); - if (specObj !== _undefined) return specObj; + var preBuiltObj = ei._getPreBuiltObjectByKeyId(key.id); + if (preBuiltObj !== _undefined) return preBuiltObj; var dir = ei._getDirectiveByKeyId(key.id); if (dir !== _undefined) return dir; @@ -397,13 +371,15 @@ export class ElementInjector extends TreeNode { if (isPresent(this._host) && this._host._isComponentKey(key)) { return this._host.getComponent(); } else { - var appInjector; - if (isPresent(requestor) && this._isComponentKey(requestor)) { - appInjector = this._shadowDomAppInjector; - } else { - appInjector = this._lightDomAppInjector; - } - return appInjector.get(key); + return this._appInjector(requestor).get(key); + } + } + + _appInjector(requestor:Key) { + if (isPresent(requestor) && this._isComponentKey(requestor)) { + return this._shadowDomAppInjector; + } else { + return this._lightDomAppInjector; } } @@ -411,9 +387,10 @@ export class ElementInjector extends TreeNode { return depth === 0; } - _getSpecialObjectByKeyId(keyId:int) { + _getPreBuiltObjectByKeyId(keyId:int) { 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 return _undefined; } diff --git a/modules/core/src/compiler/static_keys.js b/modules/core/src/compiler/static_keys.js index ea7952ea30..608a1fbd6f 100644 --- a/modules/core/src/compiler/static_keys.js +++ b/modules/core/src/compiler/static_keys.js @@ -1,4 +1,5 @@ import {View} from 'core/compiler/view'; +import {NgElement} from 'core/dom/element'; import {Key} from 'di/di'; import {isBlank} from 'facade/lang'; @@ -8,6 +9,7 @@ export class StaticKeys { constructor() { //TODO: vsavkin Key.annotate(Key.get(View), 'static') this.viewId = Key.get(View).id; + this.ngElementId = Key.get(NgElement).id; } static instance() { diff --git a/modules/core/src/compiler/view.js b/modules/core/src/compiler/view.js index 9a82eeebaa..7d8eaf4ba6 100644 --- a/modules/core/src/compiler/view.js +++ b/modules/core/src/compiler/view.js @@ -4,13 +4,14 @@ import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detectio import {Record} from 'change_detection/record'; 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 {AnnotatedType} from './annotated_type'; import {SetterFn} from 'change_detection/parser/closure_map'; import {FIELD, IMPLEMENTS, int, isPresent, isBlank} from 'facade/lang'; import {List} from 'facade/collection'; import {Injector} from 'di/di'; +import {NgElement} from 'core/dom/element'; const NG_BINDING_CLASS = 'ng-binding'; @@ -94,7 +95,6 @@ export class ProtoView { var rootElementInjectors = ProtoView._rootElementInjectors(elementInjectors); var textNodes = ProtoView._textNodes(elements, binders); var bindElements = ProtoView._bindElements(elements, binders); - ProtoView._instantiateDirectives(elementInjectors, appInjector); var viewNodes; if (clone instanceof TemplateElement) { @@ -102,8 +102,12 @@ export class ProtoView { } else { viewNodes = [clone]; } - return new View(viewNodes, elementInjectors, rootElementInjectors, textNodes, + var view = new View(viewNodes, elementInjectors, rootElementInjectors, textNodes, bindElements, this.protoWatchGroup, context); + + ProtoView._instantiateDirectives(view, elements, elementInjectors, appInjector); + + return view; } bindElement(protoElementInjector:ProtoElementInjector, @@ -173,15 +177,15 @@ export class ProtoView { } static _instantiateDirectives( - injectors:List, appInjector:Injector) { + view: View, elements:List, injectors:List, appInjector:Injector) { 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) { - //TODO: vsavkin: pass element to `proto.instantiate()` once https://github.com/angular/angular/pull/98 is merged - return proto.instantiate(parent, null, null); + return proto.instantiate(parent, null); } static _rootElementInjectors(injectors) { diff --git a/modules/core/src/dom/element.js b/modules/core/src/dom/element.js new file mode 100644 index 0000000000..2cb4c1ccc6 --- /dev/null +++ b/modules/core/src/dom/element.js @@ -0,0 +1,5 @@ +export class NgElement { + constructor(domElement) { + this.domElement = domElement; + } +} \ No newline at end of file diff --git a/modules/core/test/compiler/element_injector_spec.js b/modules/core/test/compiler/element_injector_spec.js index a90b8d6c48..08e1c50881 100644 --- a/modules/core/test/compiler/element_injector_spec.js +++ b/modules/core/test/compiler/element_injector_spec.js @@ -1,10 +1,11 @@ import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach} from 'test_lib/test_lib'; import {isBlank, isPresent, FIELD, IMPLEMENTS} from 'facade/lang'; 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 {Injector, Inject, bind} from 'di/di'; import {View} from 'core/compiler/view'; +import {NgElement} from 'core/dom/element'; @IMPLEMENTS(View) class DummyView {} @@ -60,6 +61,8 @@ class NeedsView { } export function main() { + var defaultPreBuiltObjects = new PreBuiltObjects(null, null); + function humanize(tree, names:List) { var lookupName = (item) => ListWrapper.last( @@ -70,13 +73,14 @@ export function main() { 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(props)) props = {"view": null}; var proto = new ProtoElementInjector(null, 0, bindings, isPresent(shadowDomAppInjector)); - var inj = proto.instantiate(null, null, props["view"]); - inj.instantiateDirectives(lightDomAppInjector, shadowDomAppInjector); + var inj = proto.instantiate(null, null); + var preBuilt = isPresent(preBuiltObjects) ? preBuiltObjects : defaultPreBuiltObjects; + + inj.instantiateDirectives(lightDomAppInjector, shadowDomAppInjector, preBuilt); return inj; } @@ -84,12 +88,12 @@ export function main() { var inj = new Injector([]); var protoParent = new ProtoElementInjector(null, 0, parentBindings); - var parent = protoParent.instantiate(null, null, null); - parent.instantiateDirectives(inj, null); + var parent = protoParent.instantiate(null, null); + parent.instantiateDirectives(inj, null, defaultPreBuiltObjects); var protoChild = new ProtoElementInjector(protoParent, 1, childBindings); - var child = protoChild.instantiate(parent, null, null); - child.instantiateDirectives(inj, null); + var child = protoChild.instantiate(parent, null); + child.instantiateDirectives(inj, null, defaultPreBuiltObjects); return child; } @@ -99,12 +103,12 @@ export function main() { var shadowInj = inj.createChild([]); var protoParent = new ProtoElementInjector(null, 0, hostBindings, true); - var host = protoParent.instantiate(null, null, null); - host.instantiateDirectives(inj, shadowInj); + var host = protoParent.instantiate(null, null); + host.instantiateDirectives(inj, shadowInj, null); var protoChild = new ProtoElementInjector(protoParent, 0, shadowBindings, false); - var shadow = protoChild.instantiate(null, host, null); - shadow.instantiateDirectives(shadowInj, null); + var shadow = protoChild.instantiate(null, host); + shadow.instantiateDirectives(shadowInj, null, null); return shadow; } @@ -116,9 +120,9 @@ export function main() { var protoChild1 = new ProtoElementInjector(protoParent, 1, []); var protoChild2 = new ProtoElementInjector(protoParent, 2, []); - var p = protoParent.instantiate(null, null, null); - var c1 = protoChild1.instantiate(p, null, null); - var c2 = protoChild2.instantiate(p, null, null); + var p = protoParent.instantiate(null, null); + var c1 = protoChild1.instantiate(p, null); + var c2 = protoChild2.instantiate(p, null); expect(humanize(p, [ [p, 'parent'], @@ -166,9 +170,9 @@ export function main() { 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 inj = injector([NeedsView], null, null, {'view':view}); + var inj = injector([NeedsView], null, null, new PreBuiltObjects(view, null)); expect(inj.get(NeedsView).view).toBe(view); }); @@ -258,15 +262,6 @@ export function main() { expect(() => inj.getAtIndex(10)).toThrowError( '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 () { expect(() => { @@ -275,7 +270,23 @@ export function main() { bind(B_Needs_A).toFactory((a) => new B_Needs_A(a), [A_Needs_B]) ]); }).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); }); }); });