feat(ElementInjector): add support for "special" objects

This commit is contained in:
vsavkin 2014-10-16 15:10:17 -04:00
parent e3548b497f
commit 79d270c3dd
5 changed files with 114 additions and 38 deletions

View File

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

View File

@ -8,7 +8,7 @@ export function run () {
var bindings = [A, B, C];
var proto = new ProtoElementInjector(null, bindings, []);
var ei = proto.instantiate();
var ei = proto.instantiate({view:null});
for (var i = 0; i < 20000; ++i) {
ei.clearDirectives();

View File

@ -3,8 +3,10 @@ import {Math} from 'facade/math';
import {List, ListWrapper} from 'facade/collection';
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError} from 'di/di';
import {Parent, Ancestor} from 'core/annotations/visibility';
import {StaticKeys} from './static_keys';
var MAX_DEPTH = Math.pow(2, 30) - 1;
var _undefined = new Object();
class TreeNode {
@FIELD('_parent:TreeNode')
@ -180,12 +182,13 @@ export class ProtoElementInjector extends TreeNode {
this.hasProperties = false;
}
instantiate():ElementInjector {
instantiate({view}):ElementInjector {
var p = this._parent;
var parentElementInjector = p == null ? null : p._elementInjector;
var parentElementInjector = p === null ? null : p._elementInjector;
this._elementInjector = new ElementInjector({
proto: this,
parent: parentElementInjector
parent: parentElementInjector,
view: view
});
return this._elementInjector;
}
@ -261,9 +264,11 @@ export class ElementInjector extends TreeNode {
@FIELD('_obj7:Object')
@FIELD('_obj8:Object')
@FIELD('_obj9:Object')
constructor({proto, parent}) {
@FIELD('_view:View')
constructor({proto, parent, view}) {
super(parent);
this._proto = proto;
this._view = view;
//we cannot call clearDirectives because fields won't be detected
this._appInjector = null;
@ -358,20 +363,46 @@ export class ElementInjector extends TreeNode {
return this._getByKey(dep.key, dep.depth);
}
/*
* 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 special objects only when metadata = 'special'
* we would lookup directives only when metadata = 'directive'
*
* Write benchmarks before doing this optimization.
*/
_getByKey(key:Key, depth:int) {
var ei = this;
while (ei != null && depth >= 0) {
var obj = ei._getDirectiveByKey(key);
if (isPresent(obj)) return obj;
var specObj = ei._getSpecialObjectByKey(key);
if (specObj !== _undefined) return specObj;
var dir = ei._getDirectiveByKey(key);
if (dir !== _undefined) return dir;
ei = ei._parent;
depth -= 1;
}
return this._appInjector.get(key);
}
_getSpecialObjectByKey(key:Key) {
var staticKeys = StaticKeys.instance();
var keyId = key.id;
if (keyId === staticKeys.viewId) return this._view;
//TODO add other objects as needed
return _undefined;
}
_getDirectiveByKey(key:Key) {
var p = this._proto;
var keyId= key.id;
var keyId = key.id;
if (p._keyId0 === keyId) return this._obj0;
if (p._keyId1 === keyId) return this._obj1;
if (p._keyId2 === keyId) return this._obj2;
@ -382,7 +413,7 @@ export class ElementInjector extends TreeNode {
if (p._keyId7 === keyId) return this._obj7;
if (p._keyId8 === keyId) return this._obj8;
if (p._keyId9 === keyId) return this._obj9;
return null;
return _undefined;
}
}

View File

@ -0,0 +1,17 @@
import {View} from 'core/compiler/view';
import {Key} from 'di/di';
import {isBlank} from 'facade/lang';
var _staticKeys;
export class StaticKeys {
constructor() {
//TODO: vsavkin Key.annotate(Key.get(View), 'static')
this.viewId = Key.get(View).id;
}
static instance() {
if (isBlank(_staticKeys)) _staticKeys = new StaticKeys();
return _staticKeys;
}
}

View File

@ -1,9 +1,13 @@
import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach} from 'test_lib/test_lib';
import {isBlank, FIELD} from 'facade/lang';
import {isBlank, FIELD, IMPLEMENTS} from 'facade/lang';
import {ListWrapper, MapWrapper, List} from 'facade/collection';
import {ProtoElementInjector} from 'core/compiler/element_injector';
import {ProtoElementInjector, VIEW_KEY} 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';
@IMPLEMENTS(View)
class DummyView {}
class Directive {
}
@ -36,6 +40,13 @@ class NeedsService {
}
}
class NeedsView {
@FIELD("view:Object")
constructor(@Inject(View) view) {
this.view = view;
}
}
export function main() {
function humanize(tree, names:List) {
var lookupName = (item) =>
@ -47,6 +58,30 @@ export function main() {
return [lookupName(tree), children];
}
function injector(bindings, appInjector = null, props = null) {
if (isBlank(appInjector)) appInjector = new Injector([]);
if (isBlank(props)) props = {};
var proto = new ProtoElementInjector(null, bindings, []);
var inj = proto.instantiate({view: props["view"]});
inj.instantiateDirectives(appInjector);
return inj;
}
function parentChildInjectors(parentBindings, childBindings) {
var inj = new Injector([]);
var protoParent = new ProtoElementInjector(null, parentBindings, []);
var parent = protoParent.instantiate({view: null});
parent.instantiateDirectives(inj);
var protoChild = new ProtoElementInjector(protoParent, childBindings, []);
var child = protoChild.instantiate({view: null});
child.instantiateDirectives(inj);
return child;
}
describe("ElementInjector", function () {
describe("proto injectors", function () {
it("should construct a proto tree", function () {
@ -68,9 +103,9 @@ export function main() {
var protoChild1 = new ProtoElementInjector(protoParent, [], []);
var protoChild2 = new ProtoElementInjector(protoParent, [], []);
var p = protoParent.instantiate();
var c1 = protoChild1.instantiate();
var c2 = protoChild2.instantiate();
var p = protoParent.instantiate({view: null});
var c1 = protoChild1.instantiate({view: null});
var c2 = protoChild2.instantiate({view: null});
expect(humanize(p, [
[p, 'parent'],
@ -93,29 +128,6 @@ export function main() {
});
describe("instantiateDirectives", function () {
function injector(bindings, appInjector = null) {
var proto = new ProtoElementInjector(null, bindings, []);
var inj = proto.instantiate();
if (isBlank(appInjector)) appInjector = new Injector([]);
inj.instantiateDirectives(appInjector);
return inj;
}
function parentChildInjectors(parentBindings, childBindings) {
var inj = new Injector([]);
var protoParent = new ProtoElementInjector(null, parentBindings, []);
var parent = protoParent.instantiate();
parent.instantiateDirectives(inj);
var protoChild = new ProtoElementInjector(protoParent, childBindings, []);
var child = protoChild.instantiate();
child.instantiateDirectives(inj);
return child;
}
it("should instantiate directives that have no dependencies", function () {
var inj = injector([Directive]);
expect(inj.get(Directive)).toBeAnInstanceOf(Directive);
@ -141,6 +153,13 @@ export function main() {
expect(d.service).toEqual("service");
});
it("should instantiate directives that depend on speical objects", function () {
var view = new DummyView();
var inj = injector([NeedsView], null, {"view" : view});
expect(inj.get(NeedsView).view).toBe(view);
});
it("should return app services", function () {
var appInjector = new Injector([
bind("service").toValue("service")
@ -173,5 +192,14 @@ export function main() {
toThrowError('No provider for Directive! (NeedDirectiveFromParent -> Directive)');
});
});
describe("special objects", function () {
it("should return view", function () {
var view = new DummyView();
var inj = injector([], null, {"view" : view});
expect(inj.get(View)).toEqual(view);
});
});
});
}