feat(element_injector): added PrivateComponentLocation

This commit is contained in:
vsavkin 2015-03-13 11:34:12 -07:00
parent b69f3043e0
commit 7488456d68
3 changed files with 127 additions and 4 deletions

View File

@ -7,8 +7,9 @@ import {EventEmitter, PropertySetter} 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/dom/element';
import {Directive, onChange, onDestroy} from 'angular2/src/core/annotations/annotations'
import {BindingPropagationConfig} from 'angular2/src/core/compiler/binding_propagation_config'
import {Directive, onChange, onDestroy} from 'angular2/src/core/annotations/annotations';
import {BindingPropagationConfig} from 'angular2/src/core/compiler/binding_propagation_config';
import {PrivateComponentLocation} from 'angular2/src/core/compiler/private_component_location';
import {reflector} from 'angular2/src/reflection/reflection';
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;
@ -24,6 +25,7 @@ class StaticKeys {
ngElementId:number;
viewContainerId:number;
bindingPropagationConfigId:number;
privateComponentLocationId:number;
constructor() {
//TODO: vsavkin Key.annotate(Key.get(View), 'static')
@ -31,6 +33,7 @@ class StaticKeys {
this.ngElementId = Key.get(NgElement).id;
this.viewContainerId = Key.get(ViewContainer).id;
this.bindingPropagationConfigId = Key.get(BindingPropagationConfig).id;
this.privateComponentLocationId = Key.get(PrivateComponentLocation).id;
}
static instance() {
@ -155,7 +158,6 @@ export class DirectiveBinding extends Binding {
}
}
// TODO(rado): benchmark and consider rolling in as ElementInjector fields.
export class PreBuiltObjects {
view:viewModule.View;
@ -317,6 +319,8 @@ export class ElementInjector extends TreeNode {
_obj9:any;
_preBuiltObjects;
_constructionCounter;
_privateComponent;
_privateComponentBinding:DirectiveBinding;
constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector) {
super(parent);
@ -366,6 +370,9 @@ export class ElementInjector extends TreeNode {
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._privateComponentBinding) && this._privateComponentBinding.callOnDestroy) {
this._privateComponent.onDestroy();
}
this._obj0 = null;
this._obj1 = null;
@ -377,6 +384,7 @@ export class ElementInjector extends TreeNode {
this._obj7 = null;
this._obj8 = null;
this._obj9 = null;
this._privateComponent = null;
this._constructionCounter = 0;
}
@ -399,6 +407,15 @@ export class ElementInjector extends TreeNode {
if (isPresent(p._keyId7)) this._getDirectiveByKeyId(p._keyId7);
if (isPresent(p._keyId8)) this._getDirectiveByKeyId(p._keyId8);
if (isPresent(p._keyId9)) this._getDirectiveByKeyId(p._keyId9);
if (isPresent(this._privateComponentBinding)) {
this._privateComponent = this._new(this._privateComponentBinding);
}
}
createPrivateComponent(componentType:Type, annotation:Directive) {
this._privateComponentBinding = DirectiveBinding.createFromType(componentType, annotation);
this._privateComponent = this._new(this._privateComponentBinding);
return this._privateComponent;
}
_checkShadowDomAppInjector(shadowDomAppInjector:Injector) {
@ -439,6 +456,14 @@ export class ElementInjector extends TreeNode {
}
}
getPrivateComponent() {
return this._privateComponent;
}
getShadowDomAppInjector() {
return this._shadowDomAppInjector;
}
directParent(): ElementInjector {
return this._proto.distanceToParent < 2 ? this.parent : null;
}
@ -447,6 +472,10 @@ export class ElementInjector extends TreeNode {
return this._proto._binding0IsComponent && key.id === this._proto._keyId0;
}
_isPrivateComponentKey(key:Key) {
return isPresent(this._privateComponentBinding) && key.id === this._privateComponentBinding.key.id;
}
_new(binding:Binding) {
if (this._constructionCounter++ > _MAX_DIRECTIVE_CONSTRUCTION_COUNTER) {
throw new CyclicDependencyError(binding.key);
@ -545,6 +574,8 @@ export class ElementInjector extends TreeNode {
if (isPresent(this._host) && this._host._isComponentKey(key)) {
return this._host.getComponent();
} else if (isPresent(this._host) && this._host._isPrivateComponentKey(key)) {
return this._host.getPrivateComponent();
} else if (optional) {
return this._appInjector(requestor).getOptional(key);
} else {
@ -571,6 +602,10 @@ export class ElementInjector extends TreeNode {
if (keyId === staticKeys.viewContainerId) return this._preBuiltObjects.viewContainer;
if (keyId === staticKeys.bindingPropagationConfigId) return this._preBuiltObjects.bindingPropagationConfig;
if (keyId === staticKeys.privateComponentLocationId) {
return new PrivateComponentLocation(this, this._preBuiltObjects.element, this._preBuiltObjects.view);
}
//TODO add other objects as needed
return _undefined;
}

View File

@ -0,0 +1,34 @@
import {Directive} from 'angular2/src/core/annotations/annotations'
import {NgElement} from 'angular2/src/core/dom/element';
import {ElementInjector} from './element_injector';
import {ProtoView, View} from './view';
import {ShadowDomStrategy} from './shadow_dom_strategy';
import {EventManager} from 'angular2/src/core/events/event_manager';
import {ListWrapper} from 'angular2/src/facade/collection';
import {Type} from 'angular2/src/facade/lang';
export class PrivateComponentLocation {
_elementInjector:ElementInjector;
_elt:NgElement;
_view:View;
constructor(elementInjector:ElementInjector, elt:NgElement, view:View){
this._elementInjector = elementInjector;
this._elt = elt;
this._view = view;
}
createComponent(type:Type, annotation:Directive, componentProtoView:ProtoView,
eventManager:EventManager, shadowDomStrategy:ShadowDomStrategy) {
var context = this._elementInjector.createPrivateComponent(type, annotation);
var view = componentProtoView.instantiate(this._elementInjector, eventManager);
view.hydrate(this._elementInjector.getShadowDomAppInjector(), this._elementInjector, context);
shadowDomStrategy.attachTemplate(this._elt.domElement, view);
ListWrapper.push(this._view.componentChildViews, view);
this._view.changeDetector.addChild(view.changeDetector);
}
}

View File

@ -114,6 +114,7 @@ class DirectiveWithDestroy {
export function main() {
var defaultPreBuiltObjects = new PreBuiltObjects(null, null, null, null);
var appInjector = new Injector([]);
function humanize(tree, names:List) {
var lookupName = (item) =>
@ -126,7 +127,7 @@ export function main() {
}
function injector(bindings, lightDomAppInjector = null, shadowDomAppInjector = null, preBuiltObjects = null) {
if (isBlank(lightDomAppInjector)) lightDomAppInjector = new Injector([]);
if (isBlank(lightDomAppInjector)) lightDomAppInjector = appInjector;
var proto = new ProtoElementInjector(null, 0, bindings, isPresent(shadowDomAppInjector));
var inj = proto.instantiate(null, null);
@ -447,6 +448,59 @@ export function main() {
});
});
describe("createPrivateComponent", () => {
it("should create a private component", () => {
var inj = injector([]);
inj.createPrivateComponent(SimpleDirective, null);
expect(inj.getPrivateComponent()).toBeAnInstanceOf(SimpleDirective);
});
it("should inject parent dependencies into the private component", () => {
var inj = parentChildInjectors([SimpleDirective], []);
inj.createPrivateComponent(NeedDirectiveFromAncestor, null);
expect(inj.getPrivateComponent()).toBeAnInstanceOf(NeedDirectiveFromAncestor);
expect(inj.getPrivateComponent().dependency).toBeAnInstanceOf(SimpleDirective);
});
it("should not inject the proxy component into the children of the private component", () => {
var injWithPrivateComponent = injector([SimpleDirective]);
injWithPrivateComponent.createPrivateComponent(SomeOtherDirective, null);
var shadowDomProtoInjector = new ProtoElementInjector(null, 0, [NeedDirectiveFromAncestor], false);
var shadowDomInj = shadowDomProtoInjector.instantiate(null, injWithPrivateComponent);
expect(() => shadowDomInj.instantiateDirectives(appInjector, null, defaultPreBuiltObjects)).
toThrowError(new RegExp("No provider for SimpleDirective"));
});
it("should inject the private component into the children of the private component", () => {
var injWithPrivateComponent = injector([]);
injWithPrivateComponent.createPrivateComponent(SimpleDirective, null);
var shadowDomProtoInjector = new ProtoElementInjector(null, 0, [NeedDirectiveFromAncestor], false);
var shadowDomInjector = shadowDomProtoInjector.instantiate(null, injWithPrivateComponent);
shadowDomInjector.instantiateDirectives(appInjector, null, defaultPreBuiltObjects);
expect(shadowDomInjector.get(NeedDirectiveFromAncestor)).toBeAnInstanceOf(NeedDirectiveFromAncestor);
expect(shadowDomInjector.get(NeedDirectiveFromAncestor).dependency).toBeAnInstanceOf(SimpleDirective);
});
it("should support rehydrating the private component", () => {
var inj = injector([]);
inj.createPrivateComponent(DirectiveWithDestroy, new Directive({lifecycle: [onDestroy]}));
var dir = inj.getPrivateComponent();
inj.clearDirectives();
expect(inj.getPrivateComponent()).toBe(null);
expect(dir.onDestroyCounter).toBe(1);
inj.instantiateDirectives(null, null, null);
expect(inj.getPrivateComponent()).not.toBe(null);
});
});
describe('event emitters', () => {
it('should be injectable and callable', () => {
var called = false;