From 6d246d6c72b6630f63ce2c99b44325058c818b7b Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Thu, 14 Jun 2018 15:45:21 +0200 Subject: [PATCH] fix(ivy): allow view and content queries to match the same element (#24507) When creating content queries from a directive on an element we need to take into account existing view queries. The same element can be reported to both content and view queries so freshly created content queries must be combined with pre-existing view queries. PR Close #24507 --- packages/core/src/render3/instructions.ts | 2 +- packages/core/test/render3/query_spec.ts | 68 ++++++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index c9c775a8fd..10ea38172c 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -124,7 +124,7 @@ let currentQueries: LQueries|null; export function getCurrentQueries(QueryType: {new (): LQueries}): LQueries { // top level variables should not be exported for performance reasons (PERF_NOTES.md) - return currentQueries || (currentQueries = new QueryType()); + return currentQueries || (currentQueries = (previousOrParentNode.queries || new QueryType())); } /** diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index b94e8d26fc..9efe0bdb3c 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -7,12 +7,12 @@ */ import {NgForOfContext} from '@angular/common'; -import {TemplateRef, ViewContainerRef} from '@angular/core'; +import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; import {EventEmitter} from '../..'; 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 {bind, container, containerRefreshEnd, containerRefreshStart, element, 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'; @@ -1365,4 +1365,68 @@ describe('query', () => { expect((queryInstance !.changes as EventEmitter).closed).toBeTruthy(); }); }); + + describe('content', () => { + + // https://stackblitz.com/edit/angular-wlenwd?file=src%2Fapp%2Fapp.component.ts + it('should support view and content queries matching the same element', () => { + let withContentComponentInstance: WithContentComponent; + + class WithContentComponent { + // @ContentChildren('foo') foos; + foos: QueryList; + + static ngComponentDef = defineComponent({ + type: WithContentComponent, + selectors: [['with-content']], + factory: () => { + return [new WithContentComponent(), query(null, ['foo'], true, QUERY_READ_FROM_NODE)]; + }, + template: (rf: RenderFlags, ctx: WithContentComponent) => { + // intentionally left empty, don't need anything for this test + }, + hostBindings: function ContentQueryComponent_HostBindings( + dirIndex: number, elIndex: number) { + let tmp: any; + withContentComponentInstance = loadDirective(dirIndex)[0]; + queryRefresh(tmp = loadDirective(dirIndex)[1]) && + (withContentComponentInstance.foos = tmp as QueryList); + }, + }); + } + + /** + * + *
+ *
+ *
+ * class Cmpt { + * @ViewChildren('foo, bar') foos; + * } + */ + const AppComponent = createComponent('app-component', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo', 'bar'], true, QUERY_READ_FROM_NODE); + elementStart(1, 'with-content'); + { element(2, 'div', null, ['foo', '']); } + elementEnd(); + element(4, 'div', ['id', 'after'], ['bar', '']); + } + if (rf & RenderFlags.Update) { + queryRefresh(tmp = load>(0)) && (ctx.foos = tmp as QueryList); + } + }, [WithContentComponent]); + + const fixture = new ComponentFixture(AppComponent); + const viewQList = fixture.component.foos; + + expect(viewQList.length).toBe(2); + expect(withContentComponentInstance !.foos.length).toBe(1); + expect(viewQList.first.nativeElement) + .toBe(withContentComponentInstance !.foos.first.nativeElement); + expect(viewQList.last.nativeElement.id).toBe('after'); + }); + + }); });