diff --git a/modules/angular2/src/core/compiler/dynamic_component_loader.js b/modules/angular2/src/core/compiler/dynamic_component_loader.js index 5d41fbadf9..ea1a2dc6c2 100644 --- a/modules/angular2/src/core/compiler/dynamic_component_loader.js +++ b/modules/angular2/src/core/compiler/dynamic_component_loader.js @@ -88,7 +88,7 @@ export class DynamicComponentLoader { var binding = this._getBinding(typeOrBinding); return this._compiler.compileInHost(binding).then(hostProtoViewRef => { var viewContainer = this._viewManager.getViewContainer(location); - var hostViewRef = viewContainer.create(hostProtoViewRef, viewContainer.length, injector); + var hostViewRef = viewContainer.create(hostProtoViewRef, viewContainer.length, null, injector); var newLocation = new ElementRef(hostViewRef, 0); var component = this._viewManager.getComponent(newLocation); diff --git a/modules/angular2/src/core/compiler/view_container_ref.js b/modules/angular2/src/core/compiler/view_container_ref.js index 64b57e3115..7c57d7441f 100644 --- a/modules/angular2/src/core/compiler/view_container_ref.js +++ b/modules/angular2/src/core/compiler/view_container_ref.js @@ -40,9 +40,9 @@ export class ViewContainerRef { // TODO(rado): profile and decide whether bounds checks should be added // to the methods below. - create(protoViewRef:ProtoViewRef = null, atIndex:number=-1, injector:Injector = null): ViewRef { + create(protoViewRef:ProtoViewRef = null, atIndex:number=-1, context:ElementRef, injector:Injector = null): ViewRef { if (atIndex == -1) atIndex = this.length; - return this._viewManager.createViewInContainer(this._element, atIndex, protoViewRef, injector); + return this._viewManager.createViewInContainer(this._element, atIndex, protoViewRef, context, injector); } insert(viewRef:ViewRef, atIndex:number=-1): ViewRef { @@ -68,4 +68,4 @@ export class ViewContainerRef { if (atIndex == -1) atIndex = this.length - 1; return this._viewManager.detachViewInContainer(this._element, atIndex); } -} \ No newline at end of file +} diff --git a/modules/angular2/src/core/compiler/view_manager.js b/modules/angular2/src/core/compiler/view_manager.js index fc8eb119ae..43406abb7b 100644 --- a/modules/angular2/src/core/compiler/view_manager.js +++ b/modules/angular2/src/core/compiler/view_manager.js @@ -92,16 +92,22 @@ export class AppViewManager { } createViewInContainer(viewContainerLocation:ElementRef, - atIndex:number, protoViewRef:ProtoViewRef, injector:Injector = null):ViewRef { + atIndex:number, protoViewRef:ProtoViewRef, context:ElementRef = null, injector:Injector = null):ViewRef { var protoView = internalProtoView(protoViewRef); var parentView = internalView(viewContainerLocation.parentView); var boundElementIndex = viewContainerLocation.boundElementIndex; + var contextView = null; + var contextBoundElementIndex = null; + if (isPresent(context)) { + contextView = internalView(context.parentView); + contextBoundElementIndex = context.boundElementIndex; + } var view = this._createPooledView(protoView); this._renderer.attachViewInContainer(parentView.render, boundElementIndex, atIndex, view.render); - this._utils.attachViewInContainer(parentView, boundElementIndex, atIndex, view); - this._utils.hydrateViewInContainer(parentView, boundElementIndex, atIndex, injector); + this._utils.attachViewInContainer(parentView, boundElementIndex, contextView, contextBoundElementIndex, atIndex, view); + this._utils.hydrateViewInContainer(parentView, boundElementIndex, contextView, contextBoundElementIndex, atIndex, injector); this._viewHydrateRecurse(view); return new ViewRef(view); } @@ -116,7 +122,13 @@ export class AppViewManager { var view = internalView(viewRef); var parentView = internalView(viewContainerLocation.parentView); var boundElementIndex = viewContainerLocation.boundElementIndex; - this._utils.attachViewInContainer(parentView, boundElementIndex, atIndex, view); + // TODO(tbosch): the public methods attachViewInContainer/detachViewInContainer + // are used for moving elements without the same container. + // We will change this into an atomic `move` operation, which should preserve the + // previous parent injector (see https://github.com/angular/angular/issues/1377). + // Right now we are destroying any special + // context view that might have been used. + this._utils.attachViewInContainer(parentView, boundElementIndex, null, null, atIndex, view); this._renderer.attachViewInContainer(parentView.render, boundElementIndex, atIndex, view.render); return viewRef; } diff --git a/modules/angular2/src/core/compiler/view_manager_utils.js b/modules/angular2/src/core/compiler/view_manager_utils.js index db3968fda8..e0a2408547 100644 --- a/modules/angular2/src/core/compiler/view_manager_utils.js +++ b/modules/angular2/src/core/compiler/view_manager_utils.js @@ -114,7 +114,12 @@ export class AppViewManagerUtils { } attachViewInContainer(parentView:viewModule.AppView, boundElementIndex:number, + contextView:viewModule.AppView, contextBoundElementIndex:number, atIndex:number, view:viewModule.AppView) { + if (isBlank(contextView)) { + contextView = parentView; + contextBoundElementIndex = boundElementIndex; + } parentView.changeDetector.addChild(view.changeDetector); var viewContainer = parentView.viewContainers[boundElementIndex]; if (isBlank(viewContainer)) { @@ -128,7 +133,7 @@ export class AppViewManagerUtils { } else { sibling = ListWrapper.last(viewContainer.views[atIndex - 1].rootElementInjectors) } - var elementInjector = parentView.elementInjectors[boundElementIndex]; + var elementInjector = contextView.elementInjectors[contextBoundElementIndex]; for (var i = view.rootElementInjectors.length - 1; i >= 0; i--) { view.rootElementInjectors[i].linkAfter(elementInjector, sibling); } @@ -145,11 +150,16 @@ export class AppViewManagerUtils { } hydrateViewInContainer(parentView:viewModule.AppView, boundElementIndex:number, + contextView:viewModule.AppView, contextBoundElementIndex:number, atIndex:number, injector:Injector) { + if (isBlank(contextView)) { + contextView = parentView; + contextBoundElementIndex = boundElementIndex; + } var viewContainer = parentView.viewContainers[boundElementIndex]; var view = viewContainer.views[atIndex]; - var elementInjector = parentView.elementInjectors[boundElementIndex]; - this._hydrateView(view, injector, elementInjector, parentView.context, parentView.locals); + var elementInjector = contextView.elementInjectors[contextBoundElementIndex].getHost(); + this._hydrateView(view, injector, elementInjector, contextView.context, contextView.locals); } hydrateDynamicComponentInElementInjector(hostView:viewModule.AppView, boundElementIndex:number, diff --git a/modules/angular2/src/router/router_outlet.js b/modules/angular2/src/router/router_outlet.js index 21c5197776..56d93a7f1c 100644 --- a/modules/angular2/src/router/router_outlet.js +++ b/modules/angular2/src/router/router_outlet.js @@ -37,7 +37,7 @@ export class RouterOutlet { ]); this._viewContainer.clear(); - this._viewContainer.create(pv, 0, outletInjector); + this._viewContainer.create(pv, 0, null, outletInjector); }); } diff --git a/modules/angular2/test/core/compiler/integration_spec.js b/modules/angular2/test/core/compiler/integration_spec.js index 45f3f7e201..585b4a83a7 100644 --- a/modules/angular2/test/core/compiler/integration_spec.js +++ b/modules/angular2/test/core/compiler/integration_spec.js @@ -27,11 +27,13 @@ import {PipeRegistry, defaultPipeRegistry, import {Directive, Component} from 'angular2/src/core/annotations_impl/annotations'; import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; +import {QueryList} from 'angular2/src/core/compiler/query_list'; import {View} from 'angular2/src/core/annotations_impl/view'; import {Parent, Ancestor} from 'angular2/src/core/annotations_impl/visibility'; -import {Attribute} from 'angular2/src/core/annotations_impl/di'; +import {Attribute, Query} from 'angular2/src/core/annotations_impl/di'; import {If} from 'angular2/src/directives/if'; +import {For} from 'angular2/src/directives/for'; import {ViewContainerRef} from 'angular2/src/core/compiler/view_container_ref'; import {ProtoViewRef} from 'angular2/src/core/compiler/view_ref'; @@ -341,6 +343,21 @@ export function main() { }); })); + it('should allow to transplant embedded ProtoViews into other ViewContainers', inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideView(MyComp, new View({ + template: '', + directives: [SomeDirective, CompWithParent, ToolbarComponent, ToolbarPart] + })); + + ctx.ctxProp = 'From myComp'; + tb.createView(MyComp, {context: ctx}).then((view) => { + view.detectChanges(); + expect(view.rootNodes).toHaveText('TOOLBAR(From myComp,From toolbar,Component with an injected parent)'); + + async.done(); + }); + })); + it('should assign the component instance to a var-', inject([TestBed, AsyncTestCompleter], (tb, async) => { tb.overrideView(MyComp, new View({ template: '

', @@ -951,7 +968,7 @@ class DynamicViewport { var myService = new MyService(); myService.greeting = 'dynamic greet'; this.done = compiler.compileInHost(ChildCompUsingService).then( (hostPv) => { - vc.create(hostPv, 0, inj.createChildFromResolved(Injector.resolve([bind(MyService).toValue(myService)]))) + vc.create(hostPv, 0, null, inj.createChildFromResolved(Injector.resolve([bind(MyService).toValue(myService)]))) }); } } @@ -1411,3 +1428,50 @@ class ChildComponent { this.appDependency = a; } } + +@Directive({ + selector: '[toolbar-vc]', + properties: { + 'toolbarVc': 'toolbarVc' + } +}) +class ToolbarViewContainer { + vc:ViewContainerRef; + constructor(vc:ViewContainerRef) { + this.vc = vc; + } + + set toolbarVc(part:ToolbarPart) { + var view = this.vc.create(part.protoViewRef, 0, part.elementRef); + view.setLocal('toolbarProp', 'From toolbar'); + } +} + +@Directive({ + selector: '[toolbarpart]' +}) +class ToolbarPart { + protoViewRef:ProtoViewRef; + elementRef:ElementRef; + constructor(protoViewRef:ProtoViewRef, elementRef:ElementRef) { + this.elementRef = elementRef; + this.protoViewRef = protoViewRef; + } +} + +@Component({ + selector: 'toolbar' +}) +@View({ + template: 'TOOLBAR(
)', + directives: [ToolbarViewContainer, For] +}) +class ToolbarComponent { + query:QueryList; + ctxProp:string; + + constructor(@Query(ToolbarPart) query: QueryList) { + this.ctxProp = 'hello world'; + this.query = query; + } +} diff --git a/modules/angular2/test/core/compiler/view_container_ref_spec.js b/modules/angular2/test/core/compiler/view_container_ref_spec.js index cd181c4e4a..bc139c45df 100644 --- a/modules/angular2/test/core/compiler/view_container_ref_spec.js +++ b/modules/angular2/test/core/compiler/view_container_ref_spec.js @@ -55,16 +55,20 @@ export function main() { location = new ElementRef(wrapView(view), 0); }); - it('should return a 0 length if there is no underlying ViewContainerRef', () => { - var vc = createViewContainer(); - expect(vc.length).toBe(0); - }); + describe('length', () => { + + it('should return a 0 length if there is no underlying ViewContainerRef', () => { + var vc = createViewContainer(); + expect(vc.length).toBe(0); + }); + + it('should return the size of the underlying ViewContainerRef', () => { + var vc = createViewContainer(); + view.viewContainers = [new AppViewContainer()]; + view.viewContainers[0].views = [createView()]; + expect(vc.length).toBe(1); + }); - it('should return the size of the underlying ViewContainerRef', () => { - var vc = createViewContainer(); - view.viewContainers = [new AppViewContainer()]; - view.viewContainers[0].views = [createView()]; - expect(vc.length).toBe(1); }); // TODO: add missing tests here! diff --git a/modules/angular2/test/core/compiler/view_manager_spec.js b/modules/angular2/test/core/compiler/view_manager_spec.js index 1e890ae41e..e95d72ea6e 100644 --- a/modules/angular2/test/core/compiler/view_manager_spec.js +++ b/modules/angular2/test/core/compiler/view_manager_spec.js @@ -133,7 +133,7 @@ export function main() { utils.spy('attachComponentView').andCallFake( (hostView, elementIndex, childView) => { hostView.componentChildViews[elementIndex] = childView; }); - utils.spy('attachViewInContainer').andCallFake( (parentView, elementIndex, atIndex, childView) => { + utils.spy('attachViewInContainer').andCallFake( (parentView, elementIndex, _a, _b, atIndex, childView) => { var viewContainer = parentView.viewContainers[elementIndex]; if (isBlank(viewContainer)) { viewContainer = new AppViewContainer(); @@ -411,26 +411,30 @@ export function main() { }); it('should attach the view', () => { - manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null) - expect(utils.spy('attachViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0, createdViews[0]); + var contextView = createView(); + manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), + elementRef(wrapView(contextView), 1), null); + expect(utils.spy('attachViewInContainer')).toHaveBeenCalledWith(parentView, 0, contextView, 1, 0, createdViews[0]); expect(renderer.spy('attachViewInContainer')).toHaveBeenCalledWith(parentView.render, 0, 0, createdViews[0].render); }); it('should hydrate the view', () => { var injector = new Injector([], null, false); - manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), injector); - expect(utils.spy('hydrateViewInContainer')).toHaveBeenCalledWith(parentView, 0, 0, injector); + var contextView = createView(); + manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), + elementRef(wrapView(contextView), 1), injector); + expect(utils.spy('hydrateViewInContainer')).toHaveBeenCalledWith(parentView, 0, contextView, 1, 0, injector); expect(renderer.spy('hydrateView')).toHaveBeenCalledWith(createdViews[0].render); }); it('should create and set the render view', () => { - manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null); + manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null, null); expect(renderer.spy('createView')).toHaveBeenCalledWith(childProtoView.render); expect(createdViews[0].render).toBe(createdRenderViews[0]); }); it('should set the event dispatcher', () => { - manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null); + manager.createViewInContainer(elementRef(wrapView(parentView), 0), 0, wrapPv(childProtoView), null, null); var childView = createdViews[0]; expect(renderer.spy('setEventDispatcher')).toHaveBeenCalledWith(childView.render, childView); }); diff --git a/modules/angular2/test/core/compiler/view_manager_utils_spec.js b/modules/angular2/test/core/compiler/view_manager_utils_spec.js index fd492aec59..eec658b6d7 100644 --- a/modules/angular2/test/core/compiler/view_manager_utils_spec.js +++ b/modules/angular2/test/core/compiler/view_manager_utils_spec.js @@ -23,7 +23,7 @@ import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/col import {AppProtoView, AppView} from 'angular2/src/core/compiler/view'; import {ChangeDetector} from 'angular2/change_detection'; import {ElementBinder} from 'angular2/src/core/compiler/element_binder'; -import {DirectiveBinding, ElementInjector, ElementRef} from 'angular2/src/core/compiler/element_injector'; +import {DirectiveBinding, ElementInjector, PreBuiltObjects} from 'angular2/src/core/compiler/element_injector'; import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; import {Component} from 'angular2/src/core/annotations_impl/annotations'; import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils'; @@ -66,12 +66,14 @@ export function main() { } function createElementInjector() { + var host = new SpyElementInjector(); return SpyObject.stub(new SpyElementInjector(), { 'isExportingComponent' : false, 'isExportingElement' : false, 'getEventEmitterAccessors' : [], 'getComponent' : null, - 'getDynamicallyLoadedComponent': null + 'getDynamicallyLoadedComponent': null, + 'getHost': host }, {}); } @@ -81,13 +83,15 @@ export function main() { } var view = new AppView(null, pv, MapWrapper.create()); var elementInjectors = ListWrapper.createFixedSize(pv.elementBinders.length); + var preBuiltObjects = ListWrapper.createFixedSize(pv.elementBinders.length); for (var i=0; i { + var parentView, contextView, childView; + + function createViews() { + var parentPv = createProtoView([ + createEmptyElBinder() + ]); + parentView = createView(parentPv); + + var contextPv = createProtoView([ + createEmptyElBinder() + ]); + contextView = createView(contextPv); + + var childPv = createProtoView([ + createEmptyElBinder() + ]); + childView = createView(childPv); + } + + it('should link the views rootElementInjectors after the elementInjector at the given context', () => { + createViews(); + utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView); + expect(childView.rootElementInjectors[0].spy('linkAfter')) + .toHaveBeenCalledWith(contextView.elementInjectors[0], null); + }); + + }); + + describe('hydrateViewInContainer', () => { + var parentView, contextView, childView; + + function createViews() { + var parentPv = createProtoView([ + createEmptyElBinder() + ]); + parentView = createView(parentPv); + + var contextPv = createProtoView([ + createEmptyElBinder() + ]); + contextView = createView(contextPv); + + var childPv = createProtoView([ + createEmptyElBinder() + ]); + childView = createView(childPv); + utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView); + } + + it("should instantiate the elementInjectors with the host of the context's elementInjector", () => { + createViews(); + + utils.hydrateViewInContainer(parentView, 0, contextView, 0, 0, null); + expect(childView.rootElementInjectors[0].spy('instantiateDirectives')) + .toHaveBeenCalledWith(null, contextView.elementInjectors[0].getHost(), childView.preBuiltObjects[0]); + }); + + }); + }); } @@ -190,3 +254,10 @@ class SpyChangeDetector extends SpyObject { constructor(){super(ChangeDetector);} noSuchMethod(m){return super.noSuchMethod(m)} } + +@proxy +@IMPLEMENTS(PreBuiltObjects) +class SpyPreBuiltObjects extends SpyObject { + constructor(){super(PreBuiltObjects);} + noSuchMethod(m){return super.noSuchMethod(m)} +}