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: '{{ctxProp}},{{toolbarProp}},',
+ 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)}
+}