diff --git a/modules/@angular/compiler/src/view_compiler/compile_element.ts b/modules/@angular/compiler/src/view_compiler/compile_element.ts index 8ef696b84a..5fcb028dc3 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_element.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_element.ts @@ -73,7 +73,7 @@ export class CompileElement extends CompileNode { o.THIS_EXPR.callMethod('injector', [o.literal(this.nodeIndex)])); this.instances.set( resolveIdentifierToken(Identifiers.Renderer).reference, o.THIS_EXPR.prop('renderer')); - if (this.hasViewContainer) { + if (this.hasViewContainer || this.hasEmbeddedView) { this._createViewContainer(); } if (this.component) { diff --git a/modules/@angular/compiler/src/view_compiler/view_builder.ts b/modules/@angular/compiler/src/view_compiler/view_builder.ts index 204b125639..104bf1f2c3 100644 --- a/modules/@angular/compiler/src/view_compiler/view_builder.ts +++ b/modules/@angular/compiler/src/view_compiler/view_builder.ts @@ -459,6 +459,11 @@ function createViewClass( if (view.genConfig.genDebugInfo) { superConstructorArgs.push(nodeDebugInfosVar); } + if (view.viewType === ViewType.EMBEDDED) { + viewConstructorArgs.push(new o.FnParam( + 'declaredViewContainer', o.importType(resolveIdentifier(Identifiers.ViewContainer)))); + superConstructorArgs.push(o.variable('declaredViewContainer')); + } var viewMethods = [ new o.ClassMethod( 'createInternal', [new o.FnParam(rootSelectorVar.name, o.STRING_TYPE)], @@ -676,7 +681,7 @@ function generateVisitNodesStmts( return stmts; } -function generateCreateEmbeddedViewsMethod(view: CompileView) { +function generateCreateEmbeddedViewsMethod(view: CompileView): o.ClassMethod { const nodeIndexVar = o.variable('nodeIndex'); const stmts: o.Statement[] = []; view.nodes.forEach((node) => { @@ -686,12 +691,15 @@ function generateCreateEmbeddedViewsMethod(view: CompileView) { stmts.push(new o.IfStmt( nodeIndexVar.equals(o.literal(node.nodeIndex)), [new o.ReturnStatement(node.embeddedView.classExpr.instantiate([ - ViewProperties.viewUtils, o.THIS_EXPR, o.literal(node.nodeIndex), node.renderNode + ViewProperties.viewUtils, o.THIS_EXPR, o.literal(node.nodeIndex), node.renderNode, + node.viewContainer ]))])); } } }); - stmts.push(new o.ReturnStatement(o.NULL_EXPR)); + if (stmts.length > 0) { + stmts.push(new o.ReturnStatement(o.NULL_EXPR)); + } return new o.ClassMethod( 'createEmbeddedViewInternal', [new o.FnParam(nodeIndexVar.name, o.NUMBER_TYPE)], stmts, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])); diff --git a/modules/@angular/core/src/linker/view.ts b/modules/@angular/core/src/linker/view.ts index 995af6a704..b5306aaaad 100644 --- a/modules/@angular/core/src/linker/view.ts +++ b/modules/@angular/core/src/linker/view.ts @@ -41,7 +41,7 @@ export abstract class AppView { lastRootNode: any; allNodes: any[]; disposables: Function[]; - viewContainerElement: ViewContainer = null; + viewContainer: ViewContainer = null; numberOfChecks: number = 0; @@ -58,7 +58,8 @@ export abstract class AppView { constructor( public clazz: any, public componentType: RenderComponentType, public type: ViewType, public viewUtils: ViewUtils, public parentView: AppView, public parentIndex: number, - public parentElement: any, public cdMode: ChangeDetectorStatus) { + public parentElement: any, public cdMode: ChangeDetectorStatus, + public declaredViewContainer: ViewContainer = null) { this.ref = new ViewRef_(this); if (type === ViewType.COMPONENT || type === ViewType.HOST) { this.renderer = viewUtils.renderComponent(componentType); @@ -139,8 +140,8 @@ export abstract class AppView { detachAndDestroy() { if (this._hasExternalHostElement) { this.detach(); - } else if (isPresent(this.viewContainerElement)) { - this.viewContainerElement.detachView(this.viewContainerElement.nestedViews.indexOf(this)); + } else if (isPresent(this.viewContainer)) { + this.viewContainer.detachView(this.viewContainer.nestedViews.indexOf(this)); } this.destroy(); } @@ -185,6 +186,18 @@ export abstract class AppView { } else { this._renderDetach(); } + if (this.declaredViewContainer && this.declaredViewContainer !== this.viewContainer) { + const projectedViews = this.declaredViewContainer.projectedViews; + const index = projectedViews.indexOf(this); + // perf: pop is faster than splice! + if (index >= projectedViews.length - 1) { + projectedViews.pop(); + } else { + projectedViews.splice(index, 1); + } + } + this.viewContainer = null; + this.dirtyParentQueriesInternal(); } private _renderDetach() { @@ -195,7 +208,25 @@ export abstract class AppView { } } - attachAfter(prevNode: any) { + attachAfter(viewContainer: ViewContainer, prevView: AppView) { + this._renderAttach(viewContainer, prevView); + this.viewContainer = viewContainer; + if (this.declaredViewContainer && this.declaredViewContainer !== viewContainer) { + if (!this.declaredViewContainer.projectedViews) { + this.declaredViewContainer.projectedViews = []; + } + this.declaredViewContainer.projectedViews.push(this); + } + this.dirtyParentQueriesInternal(); + } + + moveAfter(viewContainer: ViewContainer, prevView: AppView) { + this._renderAttach(viewContainer, prevView); + this.dirtyParentQueriesInternal(); + } + + private _renderAttach(viewContainer: ViewContainer, prevView: AppView) { + const prevNode = prevView ? prevView.lastRootNode : viewContainer.nativeElement; if (this._directRenderer) { const nextSibling = this._directRenderer.nextSibling(prevNode); if (nextSibling) { @@ -282,18 +313,6 @@ export abstract class AppView { */ detectChangesInternal(throwOnChange: boolean): void {} - markContentChildAsMoved(viewContainer: ViewContainer): void { this.dirtyParentQueriesInternal(); } - - addToContentChildren(viewContainer: ViewContainer): void { - this.viewContainerElement = viewContainer; - this.dirtyParentQueriesInternal(); - } - - removeFromContentChildren(viewContainer: ViewContainer): void { - this.dirtyParentQueriesInternal(); - this.viewContainerElement = null; - } - markAsCheckOnce(): void { this.cdMode = ChangeDetectorStatus.CheckOnce; } markPathToRootAsCheckOnce(): void { @@ -305,7 +324,7 @@ export abstract class AppView { if (c.type === ViewType.COMPONENT) { c = c.parentView; } else { - c = c.viewContainerElement ? c.viewContainerElement.parentView : null; + c = c.viewContainer ? c.viewContainer.parentView : null; } } } @@ -323,8 +342,11 @@ export class DebugAppView extends AppView { constructor( clazz: any, componentType: RenderComponentType, type: ViewType, viewUtils: ViewUtils, parentView: AppView, parentIndex: number, parentNode: any, cdMode: ChangeDetectorStatus, - public staticNodeDebugInfos: StaticNodeDebugInfo[]) { - super(clazz, componentType, type, viewUtils, parentView, parentIndex, parentNode, cdMode); + public staticNodeDebugInfos: StaticNodeDebugInfo[], + declaredViewContainer: ViewContainer = null) { + super( + clazz, componentType, type, viewUtils, parentView, parentIndex, parentNode, cdMode, + declaredViewContainer); } create(context: T) { diff --git a/modules/@angular/core/src/linker/view_container.ts b/modules/@angular/core/src/linker/view_container.ts index 485dbb28ff..c1812ae8fc 100644 --- a/modules/@angular/core/src/linker/view_container.ts +++ b/modules/@angular/core/src/linker/view_container.ts @@ -22,6 +22,9 @@ import {ViewType} from './view_type'; */ export class ViewContainer { public nestedViews: AppView[]; + // views that have been declared at the place of this view container, + // but inserted into another view container + public projectedViews: AppView[]; constructor( public index: number, public parentIndex: number, public parentView: AppView, @@ -59,13 +62,22 @@ export class ViewContainer { } mapNestedViews(nestedViewClass: any, callback: Function): any[] { - var result: any[] /** TODO #9100 */ = []; - if (isPresent(this.nestedViews)) { - this.nestedViews.forEach((nestedView) => { + var result: any[] = []; + if (this.nestedViews) { + for (var i = 0; i < this.nestedViews.length; i++) { + const nestedView = this.nestedViews[i]; if (nestedView.clazz === nestedViewClass) { result.push(callback(nestedView)); } - }); + } + } + if (this.projectedViews) { + for (var i = 0; i < this.projectedViews.length; i++) { + const projectedView = this.projectedViews[i]; + if (projectedView.clazz === nestedViewClass) { + result.push(callback(projectedView)); + } + } } return result; } @@ -82,17 +94,8 @@ export class ViewContainer { } nestedViews.splice(previousIndex, 1); nestedViews.splice(currentIndex, 0, view); - var refRenderNode: any /** TODO #9100 */; - if (currentIndex > 0) { - var prevView = nestedViews[currentIndex - 1]; - refRenderNode = prevView.lastRootNode; - } else { - refRenderNode = this.nativeElement; - } - if (isPresent(refRenderNode)) { - view.attachAfter(refRenderNode); - } - view.markContentChildAsMoved(this); + const prevView = currentIndex > 0 ? nestedViews[currentIndex - 1] : null; + view.moveAfter(this, prevView); } attachView(view: AppView, viewIndex: number) { @@ -110,17 +113,8 @@ export class ViewContainer { } else { nestedViews.splice(viewIndex, 0, view); } - var refRenderNode: any /** TODO #9100 */; - if (viewIndex > 0) { - var prevView = nestedViews[viewIndex - 1]; - refRenderNode = prevView.lastRootNode; - } else { - refRenderNode = this.nativeElement; - } - if (isPresent(refRenderNode)) { - view.attachAfter(refRenderNode); - } - view.addToContentChildren(this); + const prevView = viewIndex > 0 ? nestedViews[viewIndex - 1] : null; + view.attachAfter(this, prevView); } detachView(viewIndex: number): AppView { @@ -135,8 +129,6 @@ export class ViewContainer { throw new Error(`Component views can't be moved!`); } view.detach(); - - view.removeFromContentChildren(this); return view; } } diff --git a/modules/@angular/core/test/linker/query_integration_spec.ts b/modules/@angular/core/test/linker/query_integration_spec.ts index ef0a1ed505..83f60ede1f 100644 --- a/modules/@angular/core/test/linker/query_integration_spec.ts +++ b/modules/@angular/core/test/linker/query_integration_spec.ts @@ -43,7 +43,8 @@ export function main() { NeedsContentChildWithRead, NeedsViewChildrenWithRead, NeedsViewChildWithRead, - NeedsViewContainerWithRead + NeedsViewContainerWithRead, + ManualProjecting ] })); @@ -505,6 +506,25 @@ export function main() { expect(q.query4).toBeDefined(); }); }); + + describe('query over moved templates', () => { + it('should include manually projected templates in queries', () => { + const template = + ''; + const view = createTestCmpAndDetectChanges(MyComp0, template); + const q = view.debugElement.children[0].references['q']; + expect(q.query.length).toBe(0); + + q.create(); + view.detectChanges(); + expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1']); + + q.destroy(); + view.detectChanges(); + expect(q.query.length).toBe(0); + }); + + }); }); } @@ -751,3 +771,18 @@ class MyComp0 { @Component({selector: 'my-comp', template: ''}) class MyCompBroken0 { } + +@Component({selector: 'manual-projecting', template: '
'}) +class ManualProjecting { + @ContentChild(TemplateRef) template: TemplateRef; + + @ViewChild('vc', {read: ViewContainerRef}) + vc: ViewContainerRef; + + @ContentChildren(TextDirective) + query: QueryList; + + create() { this.vc.createEmbeddedView(this.template); } + + destroy() { this.vc.clear(); } +}