From 0561b66a2bad8161c49a96a86a248216aa0ce89b Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Fri, 1 Jun 2018 17:01:24 +0200 Subject: [PATCH] fix(ivy): query nodes from different TemplateRefs inserted into one ViewContainerRef (#24254) PR Close #24254 --- packages/core/src/render3/di.ts | 2 +- packages/core/src/render3/instructions.ts | 10 +- packages/core/test/render3/query_spec.ts | 120 +++++++++++++++++++++- 3 files changed, 126 insertions(+), 6 deletions(-) diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index a37b93bca8..1c48fc5c62 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -706,7 +706,7 @@ export function getOrCreateTemplateRef(di: LInjector): viewEngine_TemplateRef ngDevMode && assertNotNull(hostTNode.tViews, 'TView must be allocated'); di.templateRef = new TemplateRef( getOrCreateElementRef(di), hostTNode.tViews as TView, hostNode.data.template !, - getRenderer(), hostNode.queries); + getRenderer(), hostNode.data.queries); } return di.templateRef; } diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index cc6659c7e6..a1d23e184b 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -1514,16 +1514,20 @@ export function container( // Containers are added to the current view tree instead of their embedded views // because views can be removed and re-inserted. addToViewTree(currentView, index, node.data); + + const queries = node.queries; + if (queries) { + // prepare place for matching nodes from views inserted into a given container + lContainer.queries = queries.container(); + } + createDirectivesAndLocals(localRefs); isParent = false; ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container); - const queries = node.queries; if (queries) { // check if a given container node matches queries.addNode(node); - // prepare place for matching nodes from views inserted into a given container - lContainer.queries = queries.container(); } } diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index 9309ec5022..133c24fdb1 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -7,9 +7,10 @@ */ import {NgForOfContext} from '@angular/common'; +import {TemplateRef, ViewContainerRef} from '@angular/core'; -import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF} from '../../src/render3/di'; -import {QueryList, defineComponent, detectChanges} from '../../src/render3/index'; +import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di'; +import {AttributeMarker, QueryList, defineComponent, defineDirective, detectChanges, injectViewContainerRef} from '../../src/render3/index'; import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {query, queryRefresh} from '../../src/render3/query'; @@ -18,6 +19,7 @@ import {NgForOf, NgIf} from './common_with_def'; import {ComponentFixture, createComponent, createDirective, renderComponent} from './render_util'; + /** * Helper function to check if a given candidate object resembles ElementRef * @param candidate @@ -690,6 +692,29 @@ describe('query', () => { describe('ViewContainerRef', () => { + let directiveInstance: ViewContainerManipulatorDirective|null = null; + + class ViewContainerManipulatorDirective { + static ngDirectiveDef = defineDirective({ + type: ViewContainerManipulatorDirective, + selectors: [['', 'vc', '']], + factory: () => { + return directiveInstance = + new ViewContainerManipulatorDirective(injectViewContainerRef()); + } + }); + + constructor(private _vcRef: ViewContainerRef) {} + + insertTpl(tpl: TemplateRef<{}>, ctx: {}, idx?: number) { + this._vcRef.createEmbeddedView(tpl, ctx, idx); + } + + remove(index?: number) { this._vcRef.remove(index); } + } + + beforeEach(() => { directiveInstance = null; }); + it('should report results in views inserted / removed by ngIf', () => { /** @@ -782,6 +807,97 @@ describe('query', () => { }); + // https://stackblitz.com/edit/angular-rrmmuf?file=src/app/app.component.ts + it('should report results when different instances of TemplateRef are inserted into one ViewContainerRefs', + () => { + let tpl1: TemplateRef<{}>; + let tpl2: TemplateRef<{}>; + + + /** + * + *
+ *
+ * + *
+ * + * + *
+ *
+ * + * + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + + container(1, (rf: RenderFlags, ctx: {idx: number}) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + if (rf & RenderFlags.Update) { + elementProperty(0, 'id', bind('foo1_' + ctx.idx)); + } + }, null, []); + + elementStart(2, 'div', ['id', 'middle'], ['foo', '']); + elementEnd(); + + container(4, (rf: RenderFlags, ctx: {idx: number}) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + if (rf & RenderFlags.Update) { + elementProperty(0, 'id', bind('foo2_' + ctx.idx)); + } + }, null, []); + + container(5, undefined, null, [AttributeMarker.SELECT_ONLY, 'vc']); + } + + if (rf & RenderFlags.Update) { + tpl1 = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(1))); + tpl2 = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(4))); + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + + }, [ViewContainerManipulatorDirective]); + + const fixture = new ComponentFixture(Cmpt); + const qList = fixture.component.query; + + expect(qList.length).toBe(1); + expect(qList.first.nativeElement.getAttribute('id')).toBe('middle'); + + directiveInstance !.insertTpl(tpl1 !, {idx: 0}, 0); + directiveInstance !.insertTpl(tpl2 !, {idx: 1}, 1); + fixture.update(); + expect(qList.length).toBe(3); + let qListArr = qList.toArray(); + expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0'); + expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle'); + expect(qListArr[2].nativeElement.getAttribute('id')).toBe('foo2_1'); + + directiveInstance !.insertTpl(tpl1 !, {idx: 1}, 1); + fixture.update(); + expect(qList.length).toBe(4); + qListArr = qList.toArray(); + expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0'); + expect(qListArr[1].nativeElement.getAttribute('id')).toBe('foo1_1'); + expect(qListArr[2].nativeElement.getAttribute('id')).toBe('middle'); + expect(qListArr[3].nativeElement.getAttribute('id')).toBe('foo2_1'); + + directiveInstance !.remove(1); + fixture.update(); + expect(qList.length).toBe(3); + qListArr = qList.toArray(); + expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0'); + expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle'); + expect(qListArr[2].nativeElement.getAttribute('id')).toBe('foo2_1'); + }); }); describe('JS blocks', () => {