From 7488456d6819b230b294971b4bed250b95373142 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Fri, 13 Mar 2015 11:34:12 -0700 Subject: [PATCH] feat(element_injector): added PrivateComponentLocation --- .../src/core/compiler/element_injector.js | 41 +++++++++++++- .../compiler/private_component_location.js | 34 +++++++++++ .../core/compiler/element_injector_spec.js | 56 ++++++++++++++++++- 3 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 modules/angular2/src/core/compiler/private_component_location.js diff --git a/modules/angular2/src/core/compiler/element_injector.js b/modules/angular2/src/core/compiler/element_injector.js index c667a45007..58ded4288e 100644 --- a/modules/angular2/src/core/compiler/element_injector.js +++ b/modules/angular2/src/core/compiler/element_injector.js @@ -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; } diff --git a/modules/angular2/src/core/compiler/private_component_location.js b/modules/angular2/src/core/compiler/private_component_location.js new file mode 100644 index 0000000000..5eb343af09 --- /dev/null +++ b/modules/angular2/src/core/compiler/private_component_location.js @@ -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); + } +} diff --git a/modules/angular2/test/core/compiler/element_injector_spec.js b/modules/angular2/test/core/compiler/element_injector_spec.js index dbb38e3e2b..a229ee796a 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.js +++ b/modules/angular2/test/core/compiler/element_injector_spec.js @@ -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); @@ -446,7 +447,60 @@ export function main() { expect(inj.get(BindingPropagationConfig)).toEqual(config); }); }); + + 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;