From 3e1a3b2e327232853ca88f13aaf5b1d6d6697ffa Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Tue, 19 Jun 2018 17:58:42 +0200 Subject: [PATCH] fix(ivy): support queries for views inserted in lifecycle hooks (#24587) Closes #23707 PR Close #24587 --- packages/core/src/render3/STATUS.md | 2 +- packages/core/src/render3/component.ts | 2 +- packages/core/src/render3/component_ref.ts | 2 +- packages/core/src/render3/definition.ts | 16 +- packages/core/src/render3/instructions.ts | 44 +- .../core/src/render3/interfaces/definition.ts | 14 +- packages/core/src/render3/interfaces/view.ts | 7 +- .../hello_world/bundle.golden_symbols.json | 6 + .../bundling/todo/bundle.golden_symbols.json | 6 + .../render3/compiler_canonical/query_spec.ts | 8 +- packages/core/test/render3/di_spec.ts | 4 +- packages/core/test/render3/query_spec.ts | 1990 ++++++++++------- packages/core/test/render3/render_util.ts | 4 +- 13 files changed, 1243 insertions(+), 862 deletions(-) diff --git a/packages/core/src/render3/STATUS.md b/packages/core/src/render3/STATUS.md index bf1f5d0c10..233fdbb7ff 100644 --- a/packages/core/src/render3/STATUS.md +++ b/packages/core/src/render3/STATUS.md @@ -192,7 +192,7 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S | `@ContentChildren` | ✅ | ✅ | ❌ | | `@ContentChild` | ✅ | ✅ | ✅ | | `@ViewChildren` | ✅ | ✅ | ❌ | -| `@ViewChild` | ✅ | ✅ | ✅ | +| `@ViewChild` | ✅ | ✅ | ❌ | diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index 2009bf8f21..2888fa3138 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -107,7 +107,7 @@ export function renderComponent( const rootView: LViewData = createLViewData( rendererFactory.createRenderer(hostNode, componentDef.rendererType), - createTView(-1, null, null, null), rootContext, + createTView(-1, null, null, null, null), rootContext, componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways); rootView[INJECTOR] = opts.injector || null; diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index a3aca371ee..ea8453b4ed 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -96,7 +96,7 @@ export class ComponentFactory extends viewEngine_ComponentFactory { // Create the root view. Uses empty TView and ContentTemplate. const rootView: LViewData = createLViewData( rendererFactory.createRenderer(hostNode, this.componentDef.rendererType), - createTView(-1, null, null, null), null, + createTView(-1, null, null, null, null), null, this.componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways); rootView[INJECTOR] = ngModule && ngModule.injector || null; diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index fd6a4b08d4..f8ce00f91c 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -8,7 +8,6 @@ import {SimpleChange} from '../change_detection/change_detection_util'; import {ChangeDetectionStrategy} from '../change_detection/constants'; -import {PipeTransform} from '../change_detection/pipe_transform'; import {Provider} from '../core'; import {OnChanges, SimpleChanges} from '../metadata/lifecycle_hooks'; import {NgModuleDef, NgModuleDefInternal} from '../metadata/ng_module'; @@ -17,7 +16,7 @@ import {Type} from '../type'; import {resolveRendererType2} from '../view/util'; import {diPublic} from './di'; -import {ComponentDefFeature, ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDefFeature, DirectiveDefInternal, DirectiveDefListOrFactory, DirectiveType, DirectiveTypesOrFactory, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition'; +import {ComponentDefFeature, ComponentDefInternal, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDefFeature, DirectiveDefInternal, DirectiveDefListOrFactory, DirectiveType, DirectiveTypesOrFactory, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition'; import {CssSelectorList, SelectorFlags} from './interfaces/projection'; @@ -126,6 +125,16 @@ export function defineComponent(componentDefinition: { */ template: ComponentTemplate; + /** + * Additional set of instructions specific to view query processing. This could be seen as a + * set of instruction to be inserted into the template function. + * + * Query-related instructions need to be pulled out to a specific function as a timing of + * execution is different as compared to all other instructions (after change detection hooks but + * before view hooks). + */ + viewQuery?: ComponentQuery| null; + /** * A list of optional features to apply. * @@ -193,7 +202,8 @@ export function defineComponent(componentDefinition: { pipeDefs: pipeTypes ? () => (typeof pipeTypes === 'function' ? pipeTypes() : pipeTypes).map(extractPipeDef) : null, - selectors: componentDefinition.selectors + selectors: componentDefinition.selectors, + viewQuery: componentDefinition.viewQuery || null, }; const feature = componentDefinition.features; feature && feature.forEach((fn) => fn(def)); diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index d687622f35..88bff707a6 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -23,7 +23,7 @@ import {AttributeMarker, TAttributes, LContainerNode, LElementNode, LNode, TNode import {assertNodeType} from './node_assert'; import {appendChild, insertView, appendProjectedNode, removeView, canInsertNativeNode, createTextNode, getNextLNode, getChildLNode, getParentLNode, getLViewChild} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; -import {ComponentDefInternal, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; +import {ComponentDefInternal, ComponentTemplate, ComponentQuery, DirectiveDefInternal, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; import {isDifferent, stringify} from './util'; import {ViewRef} from './view_ref'; @@ -446,7 +446,7 @@ export function renderTemplate( if (host == null) { resetApplicationState(); rendererFactory = providedRendererFactory; - const tView = getOrCreateTView(template, directives || null, pipes || null); + const tView = getOrCreateTView(template, directives || null, pipes || null, null); host = createLNode( -1, TNodeType.Element, hostNode, null, null, createLViewData( @@ -809,7 +809,7 @@ function saveResolvedLocalsInData(): void { */ function getOrCreateTView( template: ComponentTemplate, directives: DirectiveDefListOrFactory | null, - pipes: PipeDefListOrFactory | null): TView { + pipes: PipeDefListOrFactory | null, viewQuery: ComponentQuery| null): TView { // TODO(misko): reading `ngPrivateData` here is problematic for two reasons // 1. It is a megamorphic call on each invocation. // 2. For nested embedded views (ngFor inside ngFor) the template instance is per @@ -818,7 +818,7 @@ function getOrCreateTView( // and not on embedded templates. return template.ngPrivateData || - (template.ngPrivateData = createTView(-1, template, directives, pipes) as never); + (template.ngPrivateData = createTView(-1, template, directives, pipes, viewQuery) as never); } /** @@ -830,11 +830,13 @@ function getOrCreateTView( */ export function createTView( viewIndex: number, template: ComponentTemplate| null, - directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null): TView { + directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null, + viewQuery: ComponentQuery| null): TView { ngDevMode && ngDevMode.tView++; return { id: viewIndex, template: template, + viewQuery: viewQuery, node: null !, data: HEADER_FILLER.slice(), // Fill in to match HEADER_OFFSET in LViewData childIndex: -1, // Children set in addToViewTree(), if any @@ -937,8 +939,8 @@ export function hostElement( const node = createLNode( 0, TNodeType.Element, rNode, null, null, createLViewData( - renderer, getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs), null, - def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer)); + renderer, getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs, def.viewQuery), + null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer)); if (firstTemplatePass) { node.tNode.flags = TNodeFlags.isComponent; @@ -1418,7 +1420,7 @@ export function directiveCreate( function addComponentLogic( directiveIndex: number, instance: T, def: ComponentDefInternal): void { - const tView = getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs); + const tView = getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs, def.viewQuery); // Only component views should be added to the view tree directly. Embedded views are // accessed through their containers because they may be removed / re-added later. @@ -1608,8 +1610,9 @@ export function container( appendChild(getParentLNode(node), comment, viewData); if (firstTemplatePass) { - node.tNode.tViews = - template ? createTView(-1, template, tView.directiveRegistry, tView.pipeRegistry) : []; + node.tNode.tViews = template ? + createTView(-1, template, tView.directiveRegistry, tView.pipeRegistry, null) : + []; } // Containers are added to the current view tree instead of their embedded views @@ -1775,7 +1778,7 @@ function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TV ngDevMode && assertEqual(Array.isArray(containerTViews), true, 'TViews should be in an array'); if (viewIndex >= containerTViews.length || containerTViews[viewIndex] == null) { containerTViews[viewIndex] = - createTView(viewIndex, null, tView.directiveRegistry, tView.pipeRegistry); + createTView(viewIndex, null, tView.directiveRegistry, tView.pipeRegistry, null); } return containerTViews[viewIndex]; } @@ -2198,17 +2201,34 @@ export function checkNoChanges(component: T): void { export function detectChangesInternal( hostView: LViewData, hostNode: LElementNode, component: T) { const oldView = enterView(hostView, hostNode); - const template = hostView[TVIEW].template !; + const hostTView = hostView[TVIEW]; + const template = hostTView.template !; + const viewQuery = hostTView.viewQuery; try { namespaceHTML(); + createViewQuery(viewQuery, hostView[FLAGS], component); template(getRenderFlags(hostView), component); refreshView(); + updateViewQuery(viewQuery, component); } finally { leaveView(oldView); } } +function createViewQuery( + viewQuery: ComponentQuery<{}>| null, flags: LViewFlags, component: T): void { + if (viewQuery && (flags & LViewFlags.CreationMode)) { + viewQuery(RenderFlags.Create, component); + } +} + +function updateViewQuery(viewQuery: ComponentQuery<{}>| null, component: T): void { + if (viewQuery) { + viewQuery(RenderFlags.Update, component); + } +} + /** * Mark the component as dirty (needing change detection). diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index b13d1924d0..69d685919b 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -18,6 +18,11 @@ export type ComponentTemplate = { (rf: RenderFlags, ctx: T): void; ngPrivateData?: never; }; +/** + * Definition of what a query function should look like. + */ +export type ComponentQuery = ComponentTemplate; + /** * Flags passed into template functions to determine which blocks (i.e. creation, update) * should be executed. @@ -153,15 +158,16 @@ export type ComponentDefInternal = ComponentDef; export interface ComponentDef extends DirectiveDef { /** * The View template of the component. - * - * NOTE: only used with component directives. */ readonly template: ComponentTemplate; + /** + * Query-related instructions for a component. + */ + readonly viewQuery: ComponentQuery|null; + /** * Renderer type data of the component. - * - * NOTE: only used with component directives. */ readonly rendererType: RendererType2|null; diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 5f199104cc..a98e10dd6b 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -10,7 +10,7 @@ import {Injector} from '../../di/injector'; import {Sanitizer} from '../../sanitization/security'; import {LContainer} from './container'; -import {ComponentTemplate, DirectiveDefInternal, DirectiveDefList, PipeDef, PipeDefList} from './definition'; +import {ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefList, PipeDef, PipeDefList} from './definition'; import {LElementNode, LViewNode, TNode} from './node'; import {LQueries} from './query'; import {Renderer3} from './renderer'; @@ -200,6 +200,11 @@ export interface TView { */ template: ComponentTemplate<{}>|null; + /** + * A function containing query-related instructions. + */ + viewQuery: ComponentQuery<{}>|null; + /** * Pointer to the `TNode` that represents the root of the view. * diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 6fc22af83c..3b45ff5977 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -119,6 +119,9 @@ { "name": "createTextNode" }, + { + "name": "createViewQuery" + }, { "name": "defineComponent" }, @@ -221,6 +224,9 @@ { "name": "text" }, + { + "name": "updateViewQuery" + }, { "name": "viewAttached" } diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 80369b3b06..03d1fc5334 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -266,6 +266,9 @@ { "name": "createTextNode" }, + { + "name": "createViewQuery" + }, { "name": "defineComponent" }, @@ -548,6 +551,9 @@ { "name": "tickRootContext" }, + { + "name": "updateViewQuery" + }, { "name": "viewAttached" }, diff --git a/packages/core/test/render3/compiler_canonical/query_spec.ts b/packages/core/test/render3/compiler_canonical/query_spec.ts index 98c3a6151d..4861072624 100644 --- a/packages/core/test/render3/compiler_canonical/query_spec.ts +++ b/packages/core/test/render3/compiler_canonical/query_spec.ts @@ -54,13 +54,17 @@ describe('queries', () => { factory: function ViewQueryComponent_Factory() { return new ViewQueryComponent(); }, template: function ViewQueryComponent_Template( rf: $RenderFlags$, ctx: $ViewQueryComponent$) { - let $tmp$: any; + if (rf & 1) { + $r3$.ɵEe(2, 'div', $e1_attrs$); + } + }, + viewQuery: function ViewQueryComponent_Query(rf: $RenderFlags$, ctx: $ViewQueryComponent$) { if (rf & 1) { $r3$.ɵQ(0, SomeDirective, false); $r3$.ɵQ(1, SomeDirective, false); - $r3$.ɵEe(2, 'div', $e1_attrs$); } if (rf & 2) { + let $tmp$: any; $r3$.ɵqR($tmp$ = $r3$.ɵld>(0)) && (ctx.someDir = $tmp$.first); $r3$.ɵqR($tmp$ = $r3$.ɵld>(1)) && (ctx.someDirList = $tmp$ as QueryList); diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 31db90bae7..e910548115 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -1403,8 +1403,8 @@ describe('di', () => { describe('getOrCreateNodeInjector', () => { it('should handle initial undefined state', () => { - const contentView = - createLViewData(null !, createTView(-1, null, null, null), null, LViewFlags.CheckAlways); + const contentView = createLViewData( + null !, createTView(-1, null, null, null, null), null, LViewFlags.CheckAlways); const oldView = enterView(contentView, null !); try { const parent = createLNode(0, TNodeType.Element, null, null, null, null); diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index 9efe0bdb3c..9c613e81d8 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -16,7 +16,7 @@ import {bind, container, containerRefreshEnd, containerRefreshStart, element, el import {RenderFlags} from '../../src/render3/interfaces/definition'; import {query, queryRefresh} from '../../src/render3/query'; -import {NgForOf, NgIf} from './common_with_def'; +import {NgForOf, NgIf, NgTemplateOutlet} from './common_with_def'; import {ComponentFixture, TemplateFixture, createComponent, createDirective, renderComponent} from './render_util'; @@ -54,640 +54,794 @@ describe('query', () => { let child1 = null; let child2 = null; - const Cmp = createComponent('cmp', function(rf: RenderFlags, ctx: any) { - /** - * - * - * - * - * class Cmp { - * @ViewChildren(Child) query0; - * @ViewChildren(Child, {descend: true}) query1; - * } - */ - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, Child, false); - query(1, Child, true); - elementStart(2, 'child'); - { - child1 = loadDirective(0); - elementStart(3, 'child'); - { child2 = loadDirective(1); } - elementEnd(); - } - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query0 = tmp as QueryList); - queryRefresh(tmp = load>(1)) && (ctx.query1 = tmp as QueryList); - } - }, [Child]); + const Cmp = createComponent( + 'cmp', + function(rf: RenderFlags, ctx: any) { + /** + * + * + * + * + * class Cmp { + * @ViewChildren(Child) query0; + * @ViewChildren(Child, {descend: true}) query1; + * } + */ + if (rf & RenderFlags.Create) { + elementStart(2, 'child'); + { element(3, 'child'); } + elementEnd(); + } + if (rf & RenderFlags.Update) { + child1 = loadDirective(0); + child2 = loadDirective(1); + } + }, + [Child], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, Child, false); + query(1, Child, true); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query0 = tmp as QueryList); + queryRefresh(tmp = load>(1)) && (ctx.query1 = tmp as QueryList); + } + }); const parent = renderComponent(Cmp); expect((parent.query0 as QueryList).toArray()).toEqual([child1]); expect((parent.query1 as QueryList).toArray()).toEqual([child1, child2]); }); - describe('types predicate', () => { + describe('predicate', () => { + describe('types', () => { - it('should query using type predicate and read a specified token', () => { - const Child = createDirective('child'); - let elToQuery; - /** - *
- * class Cmpt { - * @ViewChildren(Child, {read: ElementRef}) query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, Child, false, QUERY_READ_ELEMENT_REF); - elToQuery = elementStart(1, 'div', ['child', '']); - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }, [Child]); + it('should query using type predicate and read a specified token', () => { + const Child = createDirective('child'); + let elToQuery; + /** + *
+ * class Cmpt { + * @ViewChildren(Child, {read: ElementRef}) query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elToQuery = elementStart(1, 'div', ['child', '']); + elementEnd(); + } + }, + [Child], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, Child, false, QUERY_READ_ELEMENT_REF); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(1); - expect(isElementRef(qList.first)).toBeTruthy(); - expect(qList.first.nativeElement).toEqual(elToQuery); - }); - - - it('should query using type predicate and read another directive type', () => { - const Child = createDirective('child'); - const OtherChild = createDirective('otherChild'); - let otherChildInstance; - /** - *
- * class Cmpt { - * @ViewChildren(Child, {read: OtherChild}) query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, Child, false, OtherChild); - elementStart(1, 'div', ['child', '', 'otherChild', '']); - { otherChildInstance = loadDirective(1); } - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }, [Child, OtherChild]); - - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(1); - expect(qList.first).toBe(otherChildInstance); - }); - - it('should not add results to query if a requested token cant be read', () => { - const Child = createDirective('child'); - const OtherChild = createDirective('otherChild'); - /** - *
- * class Cmpt { - * @ViewChildren(Child, {read: OtherChild}) query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, Child, false, OtherChild); - elementStart(1, 'div', ['child', '']); - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }, [Child, OtherChild]); - - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(0); - }); - }); - - describe('local names predicate', () => { - - it('should query for a single element and read ElementRef by default', () => { - - let elToQuery; - /** - *
- *
- * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], false, QUERY_READ_FROM_NODE); - elToQuery = elementStart(1, 'div', null, ['foo', '']); - elementEnd(); - elementStart(3, 'div'); - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(1); + expect(isElementRef(qList.first)).toBeTruthy(); + expect(qList.first.nativeElement).toBe(elToQuery); }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(1); - expect(qList.first.nativeElement).toEqual(elToQuery); - }); + it('should query using type predicate and read another directive type', () => { + const Child = createDirective('child'); + const OtherChild = createDirective('otherChild'); + let otherChildInstance; + /** + *
+ * class Cmpt { + * @ViewChildren(Child, {read: OtherChild}) query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(1, 'div', ['child', '', 'otherChild', '']); + { otherChildInstance = loadDirective(1); } + elementEnd(); + } + }, + [Child, OtherChild], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, Child, false, OtherChild); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); - it('should query multiple locals on the same element', () => { - let elToQuery; - - /** - *
- *
- * class Cmpt { - * @ViewChildren('foo') fooQuery; - * @ViewChildren('bar') barQuery; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], false, QUERY_READ_FROM_NODE); - query(1, ['bar'], false, QUERY_READ_FROM_NODE); - elToQuery = elementStart(2, 'div', null, ['foo', '', 'bar', '']); - elementEnd(); - elementStart(5, 'div'); - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.fooQuery = tmp as QueryList); - queryRefresh(tmp = load>(1)) && (ctx.barQuery = tmp as QueryList); - } + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(1); + expect(qList.first).toBe(otherChildInstance); }); - const cmptInstance = renderComponent(Cmpt); + it('should not add results to query if a requested token cant be read', () => { + const Child = createDirective('child'); + const OtherChild = createDirective('otherChild'); + /** + *
+ * class Cmpt { + * @ViewChildren(Child, {read: OtherChild}) query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(1, 'div', ['child', '']); + elementEnd(); + } + }, + [Child, OtherChild], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, Child, false, OtherChild); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); - const fooList = (cmptInstance.fooQuery as QueryList); - expect(fooList.length).toBe(1); - expect(fooList.first.nativeElement).toEqual(elToQuery); - - const barList = (cmptInstance.barQuery as QueryList); - expect(barList.length).toBe(1); - expect(barList.first.nativeElement).toEqual(elToQuery); + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(0); + }); }); - it('should query for multiple elements and read ElementRef by default', () => { + describe('local names', () => { - let el1ToQuery; - let el2ToQuery; - /** - *
- *
- *
- * class Cmpt { - * @ViewChildren('foo,bar') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo', 'bar'], undefined, QUERY_READ_FROM_NODE); - el1ToQuery = elementStart(1, 'div', null, ['foo', '']); - elementEnd(); - elementStart(3, 'div'); - elementEnd(); - el2ToQuery = elementStart(4, 'div', null, ['bar', '']); - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } + it('should query for a single element and read ElementRef by default', () => { + + let elToQuery; + /** + *
+ *
+ * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elToQuery = elementStart(1, 'div', null, ['foo', '']); + elementEnd(); + elementStart(3, 'div'); + elementEnd(); + } + }, + [], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], false, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(1); + expect(qList.first.nativeElement).toEqual(elToQuery); }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(2); - expect(qList.first.nativeElement).toEqual(el1ToQuery); - expect(qList.last.nativeElement).toEqual(el2ToQuery); - }); + it('should query multiple locals on the same element', () => { + let elToQuery; - it('should read ElementRef from an element when explicitly asked for', () => { + /** + *
+ *
+ * class Cmpt { + * @ViewChildren('foo') fooQuery; + * @ViewChildren('bar') barQuery; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elToQuery = elementStart(2, 'div', null, ['foo', '', 'bar', '']); + elementEnd(); + elementStart(5, 'div'); + elementEnd(); + } + }, + [], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], false, QUERY_READ_FROM_NODE); + query(1, ['bar'], false, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && + (ctx.fooQuery = tmp as QueryList); + queryRefresh(tmp = load>(1)) && + (ctx.barQuery = tmp as QueryList); + } + }); - let elToQuery; - /** - *
- *
- * class Cmpt { - * @ViewChildren('foo', {read: ElementRef}) query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], false, QUERY_READ_ELEMENT_REF); - elToQuery = elementStart(1, 'div', null, ['foo', '']); - elementEnd(); - elementStart(3, 'div'); - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } + const cmptInstance = renderComponent(Cmpt); + + const fooList = (cmptInstance.fooQuery as QueryList); + expect(fooList.length).toBe(1); + expect(fooList.first.nativeElement).toEqual(elToQuery); + + const barList = (cmptInstance.barQuery as QueryList); + expect(barList.length).toBe(1); + expect(barList.first.nativeElement).toEqual(elToQuery); }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(1); - expect(isElementRef(qList.first)).toBeTruthy(); - expect(qList.first.nativeElement).toEqual(elToQuery); - }); + it('should query for multiple elements and read ElementRef by default', () => { - it('should read ViewContainerRef from element nodes when explicitly asked for', () => { - /** - *
- * class Cmpt { - * @ViewChildren('foo', {read: ViewContainerRef}) query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], false, QUERY_READ_CONTAINER_REF); - elementStart(1, 'div', null, ['foo', '']); - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } + let el1ToQuery; + let el2ToQuery; + /** + *
+ *
+ *
+ * class Cmpt { + * @ViewChildren('foo,bar') query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + el1ToQuery = elementStart(1, 'div', null, ['foo', '']); + elementEnd(); + elementStart(3, 'div'); + elementEnd(); + el2ToQuery = elementStart(4, 'div', null, ['bar', '']); + elementEnd(); + } + }, + [], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo', 'bar'], undefined, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(2); + expect(qList.first.nativeElement).toEqual(el1ToQuery); + expect(qList.last.nativeElement).toEqual(el2ToQuery); }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(1); - expect(isViewContainerRef(qList.first)).toBeTruthy(); - }); + it('should read ElementRef from an element when explicitly asked for', () => { - it('should read ViewContainerRef from container nodes when explicitly asked for', () => { - /** - * - * class Cmpt { - * @ViewChildren('foo', {read: ViewContainerRef}) query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], false, QUERY_READ_CONTAINER_REF); - container(1, undefined, undefined, undefined, ['foo', '']); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } + let elToQuery; + /** + *
+ *
+ * class Cmpt { + * @ViewChildren('foo', {read: ElementRef}) query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elToQuery = elementStart(1, 'div', null, ['foo', '']); + elementEnd(); + element(3, 'div'); + } + }, + [], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], false, QUERY_READ_ELEMENT_REF); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(1); + expect(isElementRef(qList.first)).toBeTruthy(); + expect(qList.first.nativeElement).toEqual(elToQuery); }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(1); - expect(isViewContainerRef(qList.first)).toBeTruthy(); - }); + it('should read ViewContainerRef from element nodes when explicitly asked for', () => { + /** + *
+ * class Cmpt { + * @ViewChildren('foo', {read: ViewContainerRef}) query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + element(1, 'div', null, ['foo', '']); + } + }, + [], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], false, QUERY_READ_CONTAINER_REF); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); - it('should read ElementRef with a native element pointing to comment DOM node from containers', - () => { - /** - * - * class Cmpt { - * @ViewChildren('foo', {read: ElementRef}) query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], false, QUERY_READ_ELEMENT_REF); - container(1, undefined, undefined, undefined, ['foo', '']); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(1); + expect(isViewContainerRef(qList.first)).toBeTruthy(); + }); + + it('should read ViewContainerRef from container nodes when explicitly asked for', () => { + /** + * + * class Cmpt { + * @ViewChildren('foo', {read: ViewContainerRef}) query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + container(1, undefined, undefined, undefined, ['foo', '']); + } + }, + [], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], false, QUERY_READ_CONTAINER_REF); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(1); + expect(isViewContainerRef(qList.first)).toBeTruthy(); + }); + + it('should read ElementRef with a native element pointing to comment DOM node from containers', + () => { + /** + * + * class Cmpt { + * @ViewChildren('foo', {read: ElementRef}) query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + container(1, undefined, undefined, undefined, ['foo', '']); + } + }, + [], [], + function(rf: RenderFlags, ctx: any) { + + if (rf & RenderFlags.Create) { + query(0, ['foo'], false, QUERY_READ_ELEMENT_REF); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && + (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(1); + expect(isElementRef(qList.first)).toBeTruthy(); + expect(qList.first.nativeElement.nodeType).toBe(8); // Node.COMMENT_NODE = 8 }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(1); - expect(isElementRef(qList.first)).toBeTruthy(); - expect(qList.first.nativeElement.nodeType).toBe(8); // Node.COMMENT_NODE = 8 - }); + it('should read TemplateRef from container nodes by default', () => { + // http://plnkr.co/edit/BVpORly8wped9I3xUYsX?p=preview + /** + * + * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + container(1, undefined, undefined, undefined, ['foo', '']); + } + }, + [], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], undefined, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); - it('should read TemplateRef from container nodes by default', () => { - // http://plnkr.co/edit/BVpORly8wped9I3xUYsX?p=preview - /** - * - * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], undefined, QUERY_READ_FROM_NODE); - container(1, undefined, undefined, undefined, ['foo', '']); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(1); + expect(isTemplateRef(qList.first)).toBeTruthy(); }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(1); - expect(isTemplateRef(qList.first)).toBeTruthy(); - }); + it('should read TemplateRef from container nodes when explicitly asked for', () => { + /** + * + * class Cmpt { + * @ViewChildren('foo', {read: TemplateRef}) query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + container(1, undefined, undefined, undefined, ['foo', '']); + } + }, + [], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], false, QUERY_READ_TEMPLATE_REF); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); - it('should read TemplateRef from container nodes when explicitly asked for', () => { - /** - * - * class Cmpt { - * @ViewChildren('foo', {read: TemplateRef}) query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], false, QUERY_READ_TEMPLATE_REF); - container(1, undefined, undefined, undefined, ['foo', '']); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(1); + expect(isTemplateRef(qList.first)).toBeTruthy(); + }); + + it('should read component instance if element queried for is a component host', () => { + const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {}); + + let childInstance; + /** + * + * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + element(1, 'child', null, ['foo', '']); + } + if (rf & RenderFlags.Update) { + childInstance = loadDirective(0); + } + }, + [Child], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(1); + expect(qList.first).toBe(childInstance); + }); + + it('should read component instance with explicit exportAs', () => { + let childInstance: Child; + + class Child { + static ngComponentDef = defineComponent({ + type: Child, + selectors: [['child']], + factory: () => childInstance = new Child(), + template: (rf: RenderFlags, ctx: Child) => {}, + exportAs: 'child' + }); + } + + /** + * + * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + element(1, 'child', null, ['foo', 'child']); + } + }, + [Child], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(1); + expect(qList.first).toBe(childInstance !); + }); + + it('should read directive instance if element queried for has an exported directive with a matching name', + () => { + const Child = createDirective('child', {exportAs: 'child'}); + + let childInstance; + /** + *
+ * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + element(1, 'div', ['child', ''], ['foo', 'child']); + } + if (rf & RenderFlags.Update) { + childInstance = loadDirective(0); + } + }, + [Child], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && + (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(1); + expect(qList.first).toBe(childInstance); + }); + + it('should read all matching directive instances from a given element', () => { + const Child1 = createDirective('child1', {exportAs: 'child1'}); + const Child2 = createDirective('child2', {exportAs: 'child2'}); + + let child1Instance, child2Instance; + /** + *
+ * class Cmpt { + * @ViewChildren('foo, bar') query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + element(1, 'div', ['child1', '', 'child2', ''], ['foo', 'child1', 'bar', 'child2']); + } + if (rf & RenderFlags.Update) { + child1Instance = loadDirective(0); + child2Instance = loadDirective(1); + } + }, + [Child1, Child2], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo', 'bar'], true, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(2); + expect(qList.first).toBe(child1Instance); + expect(qList.last).toBe(child2Instance); + }); + + it('should read multiple locals exporting the same directive from a given element', () => { + const Child = createDirective('child', {exportAs: 'child'}); + let childInstance; + + /** + *
+ * class Cmpt { + * @ViewChildren('foo') fooQuery; + * @ViewChildren('bar') barQuery; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + element(2, 'div', ['child', ''], ['foo', 'child', 'bar', 'child']); + } + if (rf & RenderFlags.Update) { + childInstance = loadDirective(0); + } + }, + [Child], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + query(1, ['bar'], true, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && + (ctx.fooQuery = tmp as QueryList); + queryRefresh(tmp = load>(1)) && + (ctx.barQuery = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + + const fooList = cmptInstance.fooQuery as QueryList; + expect(fooList.length).toBe(1); + expect(fooList.first).toBe(childInstance); + + const barList = cmptInstance.barQuery as QueryList; + expect(barList.length).toBe(1); + expect(barList.first).toBe(childInstance); + }); + + it('should match on exported directive name and read a requested token', () => { + const Child = createDirective('child', {exportAs: 'child'}); + + let div; + /** + *
+ * class Cmpt { + * @ViewChildren('foo', {read: ElementRef}) query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + div = elementStart(1, 'div', ['child', ''], ['foo', 'child']); + elementEnd(); + } + }, + [Child], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], undefined, QUERY_READ_ELEMENT_REF); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(1); + expect(qList.first.nativeElement).toBe(div); + }); + + it('should support reading a mix of ElementRef and directive instances', () => { + const Child = createDirective('child', {exportAs: 'child'}); + + let childInstance, div; + /** + *
+ * class Cmpt { + * @ViewChildren('foo, bar') query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + div = elementStart(1, 'div', ['child', ''], ['foo', '', 'bar', 'child']); + elementEnd(); + } + if (rf & RenderFlags.Update) { + childInstance = loadDirective(0); + } + }, + [Child], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo', 'bar'], undefined, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(2); + expect(qList.first.nativeElement).toBe(div); + expect(qList.last).toBe(childInstance); + }); + + it('should not add results to query if a requested token cant be read', () => { + const Child = createDirective('child'); + + /** + *
+ * class Cmpt { + * @ViewChildren('foo', {read: Child}) query; + * } + */ + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + element(1, 'div', ['foo', '']); + } + }, + [Child], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], false, Child); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as QueryList); + expect(qList.length).toBe(0); }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(1); - expect(isTemplateRef(qList.first)).toBeTruthy(); }); - - it('should read component instance if element queried for is a component host', () => { - const Child = createComponent('child', function(rf: RenderFlags, ctx: any) {}); - - let childInstance; - /** - * - * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - elementStart(1, 'child', null, ['foo', '']); - { childInstance = loadDirective(0); } - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }, [Child]); - - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(1); - expect(qList.first).toBe(childInstance); - }); - - it('should read component instance with explicit exportAs', () => { - let childInstance: Child; - - class Child { - static ngComponentDef = defineComponent({ - type: Child, - selectors: [['child']], - factory: () => childInstance = new Child(), - template: (rf: RenderFlags, ctx: Child) => {}, - exportAs: 'child' - }); - } - - /** - * - * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - elementStart(1, 'child', null, ['foo', 'child']); - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }, [Child]); - - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(1); - expect(qList.first).toBe(childInstance !); - }); - - it('should read directive instance if element queried for has an exported directive with a matching name', - () => { - const Child = createDirective('child', {exportAs: 'child'}); - - let childInstance; - /** - *
- * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - elementStart(1, 'div', ['child', ''], ['foo', 'child']); - childInstance = loadDirective(0); - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }, [Child]); - - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(1); - expect(qList.first).toBe(childInstance); - }); - - it('should read all matching directive instances from a given element', () => { - const Child1 = createDirective('child1', {exportAs: 'child1'}); - const Child2 = createDirective('child2', {exportAs: 'child2'}); - - let child1Instance, child2Instance; - /** - *
- * class Cmpt { - * @ViewChildren('foo, bar') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo', 'bar'], true, QUERY_READ_FROM_NODE); - elementStart(1, 'div', ['child1', '', 'child2', ''], ['foo', 'child1', 'bar', 'child2']); - { - child1Instance = loadDirective(0); - child2Instance = loadDirective(1); - } - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }, [Child1, Child2]); - - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(2); - expect(qList.first).toBe(child1Instance); - expect(qList.last).toBe(child2Instance); - }); - - it('should read multiple locals exporting the same directive from a given element', () => { - const Child = createDirective('child', {exportAs: 'child'}); - let childInstance; - - /** - *
- * class Cmpt { - * @ViewChildren('foo') fooQuery; - * @ViewChildren('bar') barQuery; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - query(1, ['bar'], true, QUERY_READ_FROM_NODE); - elementStart(2, 'div', ['child', ''], ['foo', 'child', 'bar', 'child']); - { childInstance = loadDirective(0); } - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.fooQuery = tmp as QueryList); - queryRefresh(tmp = load>(1)) && (ctx.barQuery = tmp as QueryList); - } - }, [Child]); - - const cmptInstance = renderComponent(Cmpt); - - const fooList = cmptInstance.fooQuery as QueryList; - expect(fooList.length).toBe(1); - expect(fooList.first).toBe(childInstance); - - const barList = cmptInstance.barQuery as QueryList; - expect(barList.length).toBe(1); - expect(barList.first).toBe(childInstance); - }); - - it('should match on exported directive name and read a requested token', () => { - const Child = createDirective('child', {exportAs: 'child'}); - - let div; - /** - *
- * class Cmpt { - * @ViewChildren('foo', {read: ElementRef}) query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], undefined, QUERY_READ_ELEMENT_REF); - div = elementStart(1, 'div', ['child', ''], ['foo', 'child']); - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }, [Child]); - - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(1); - expect(qList.first.nativeElement).toBe(div); - }); - - it('should support reading a mix of ElementRef and directive instances', () => { - const Child = createDirective('child', {exportAs: 'child'}); - - let childInstance, div; - /** - *
- * class Cmpt { - * @ViewChildren('foo, bar') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo', 'bar'], undefined, QUERY_READ_FROM_NODE); - div = elementStart(1, 'div', ['child', ''], ['foo', '', 'bar', 'child']); - { childInstance = loadDirective(0); } - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }, [Child]); - - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(2); - expect(qList.first.nativeElement).toBe(div); - expect(qList.last).toBe(childInstance); - }); - - it('should not add results to query if a requested token cant be read', () => { - const Child = createDirective('child'); - - /** - *
- * class Cmpt { - * @ViewChildren('foo', {read: Child}) query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], false, Child); - elementStart(1, 'div', ['foo', '']); - elementEnd(); - } - if (rf & RenderFlags.Update) { - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }, [Child]); - - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as QueryList); - expect(qList.length).toBe(0); - }); - }); describe('view boundaries', () => { @@ -729,22 +883,31 @@ describe('query', () => { * @ViewChildren('foo') query; * } */ - 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, (rf1: RenderFlags, ctx1: any) => { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'div', null, ['foo', '']); - elementEnd(); + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + container(1, (rf1: RenderFlags, ctx1: any) => { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + }, null, ['ngIf', '']); } - }, null, ['ngIf', '']); - } - if (rf & RenderFlags.Update) { - elementProperty(1, 'ngIf', bind(ctx.value)); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }, [NgIf]); + if (rf & RenderFlags.Update) { + elementProperty(1, 'ngIf', bind(ctx.value)); + } + }, + [NgIf], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); const fixture = new ComponentFixture(Cmpt); const qList = fixture.component.query; @@ -769,25 +932,41 @@ describe('query', () => { * @ViewChildren('foo') query; * } */ - 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, (rf1: RenderFlags, row: NgForOfContext) => { - if (rf1 & RenderFlags.Create) { - elementStart(0, 'div', null, ['foo', '']); - elementEnd(); + class Cmpt { + value: string[]; + query: any; + static ngComponentDef = defineComponent({ + type: Cmpt, + factory: () => new Cmpt(), + selectors: [['my-app']], + template: function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + container(1, (rf1: RenderFlags, row: NgForOfContext) => { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + elementProperty(0, 'id', bind(row.$implicit)); + } + }, null, ['ngForOf', '']); } - if (rf1 & RenderFlags.Update) { - elementProperty(0, 'id', bind(row.$implicit)); + if (rf & RenderFlags.Update) { + elementProperty(1, 'ngForOf', bind(ctx.value)); } - }, null, ['ngForOf', '']); - } - if (rf & RenderFlags.Update) { - elementProperty(1, 'ngForOf', bind(ctx.value)); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }, [NgForOf]); + }, + viewQuery: function(rf: RenderFlags, ctx: Cmpt) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }, + directives: () => [NgForOf] + }); + } const fixture = new ComponentFixture(Cmpt); const qList = fixture.component.query; @@ -795,14 +974,10 @@ describe('query', () => { fixture.component.value = ['a', 'b', 'c']; fixture.update(); - fixture - .update(); // invoking CD twice due to https://github.com/angular/angular/issues/23707 expect(qList.length).toBe(3); fixture.component.value.splice(1, 1); // remove "b" fixture.update(); - fixture - .update(); // invoking CD twice due to https://github.com/angular/angular/issues/23707 expect(qList.length).toBe(2); // make sure that a proper element was removed from query results @@ -831,44 +1006,53 @@ describe('query', () => { * * */ - 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}) => { + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { - elementStart(0, 'div', null, ['foo', '']); + 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.SelectOnly, 'vc']); + } + + if (rf & RenderFlags.Update) { + tpl1 = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(1))); + tpl2 = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(4))); + } + + }, + [ViewContainerManipulatorDirective], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); } if (rf & RenderFlags.Update) { - elementProperty(0, 'id', bind('foo1_' + ctx.idx)); + let tmp: any; + queryRefresh(tmp = load>(0)) && + (ctx.query = tmp as QueryList); } - }, 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.SelectOnly, '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; @@ -923,32 +1107,47 @@ describe('query', () => { * * */ - 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, container_idx: number}) => { + class Cmpt { + query: any; + static ngComponentDef = defineComponent({ + type: Cmpt, + factory: () => new Cmpt(), + selectors: [['my-app']], + template: function(rf: RenderFlags, ctx: any) { + let tmp: any; if (rf & RenderFlags.Create) { - elementStart(0, 'div', null, ['foo', '']); - elementEnd(); + container(1, (rf: RenderFlags, ctx: {idx: number, container_idx: number}) => { + if (rf & RenderFlags.Create) { + elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + if (rf & RenderFlags.Update) { + elementProperty(0, 'id', bind('foo_' + ctx.container_idx + '_' + ctx.idx)); + } + }, null, []); + + container(2, undefined, null, [AttributeMarker.SelectOnly, 'vc']); + container(3, undefined, null, [AttributeMarker.SelectOnly, 'vc']); + } + + if (rf & RenderFlags.Update) { + tpl = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(1))); + } + + }, + viewQuery: (rf: RenderFlags, cmpt: Cmpt) => { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); } if (rf & RenderFlags.Update) { - elementProperty(0, 'id', bind('foo_' + ctx.container_idx + '_' + ctx.idx)); + queryRefresh(tmp = load>(0)) && + (cmpt.query = tmp as QueryList); } - }, null, []); - - container(2, undefined, null, [AttributeMarker.SelectOnly, 'vc']); - container(3, undefined, null, [AttributeMarker.SelectOnly, 'vc']); - } - - if (rf & RenderFlags.Update) { - tpl = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(1))); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - - }, [ViewContainerManipulatorDirective]); - + }, + directives: () => [ViewContainerManipulatorDirective], + }); + } const fixture = new ComponentFixture(Cmpt); const qList = fixture.component.query; @@ -972,6 +1171,64 @@ describe('query', () => { fixture.update(); expect(qList.length).toBe(0); }); + + // https://stackblitz.com/edit/angular-wpd6gv?file=src%2Fapp%2Fapp.component.ts + it('should report results from views inserted in a lifecycle hook', () => { + + class MyApp { + show = false; + query: any; + static ngComponentDef = defineComponent({ + type: MyApp, + factory: () => new MyApp(), + selectors: [['my-app']], + /** + * from tpl + * + */ + template: (rf: RenderFlags, myApp: MyApp) => { + if (rf & RenderFlags.Create) { + container(1, (rf1: RenderFlags) => { + if (rf1 & RenderFlags.Create) { + element(0, 'span', ['id', 'from_tpl'], ['foo', '']); + } + }, undefined, undefined, ['tpl', '']); + container(3, undefined, null, [AttributeMarker.SelectOnly, 'ngTemplateOutlet']); + } + if (rf & RenderFlags.Update) { + const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(1))); + elementProperty(3, 'ngTemplateOutlet', bind(myApp.show ? tplRef : null)); + } + }, + directives: () => [NgTemplateOutlet], + viewQuery: (rf: RenderFlags, myApp: MyApp) => { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + queryRefresh(tmp = load>(0)) && + (myApp.query = tmp as QueryList); + } + } + }); + } + + const fixture = new ComponentFixture(MyApp); + const qList = fixture.component.query; + + expect(qList.length).toBe(0); + + fixture.component.show = true; + fixture.update(); + expect(qList.length).toBe(1); + expect(qList.first.nativeElement.id).toBe('from_tpl'); + + fixture.component.show = false; + fixture.update(); + expect(qList.length).toBe(0); + }); + }); describe('JS blocks', () => { @@ -986,30 +1243,39 @@ describe('query', () => { * @ViewChildren('foo') query; * } */ - 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); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(1); - { - if (ctx.exp) { - let rf1 = embeddedViewStart(1); + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + container(1); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(1); { - if (rf1 & RenderFlags.Create) { - firstEl = elementStart(0, 'div', null, ['foo', '']); - elementEnd(); + if (ctx.exp) { + let rf1 = embeddedViewStart(1); + { + if (rf1 & RenderFlags.Create) { + firstEl = elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); } } - embeddedViewEnd(); + containerRefreshEnd(); } - } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }); + }, + [], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); const cmptInstance = renderComponent(Cmpt); const qList = (cmptInstance.query as any); @@ -1038,34 +1304,44 @@ describe('query', () => { * @ViewChildren('foo') query; * } */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - firstEl = elementStart(1, 'span', null, ['foo', '']); - elementEnd(); - container(3); - lastEl = elementStart(4, 'span', null, ['foo', '']); - elementEnd(); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(3); - { - if (ctx.exp) { - let rf1 = embeddedViewStart(1); + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + firstEl = elementStart(1, 'span', null, ['foo', '']); + elementEnd(); + container(3); + lastEl = elementStart(4, 'span', null, ['foo', '']); + elementEnd(); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(3); { - if (rf1 & RenderFlags.Create) { - viewEl = elementStart(0, 'div', null, ['foo', '']); - elementEnd(); + if (ctx.exp) { + let rf1 = embeddedViewStart(1); + { + if (rf1 & RenderFlags.Create) { + viewEl = elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); } } - embeddedViewEnd(); + containerRefreshEnd(); } - } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }); + }, + [], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && + (ctx.query = tmp as QueryList); + } + }); const cmptInstance = renderComponent(Cmpt); const qList = (cmptInstance.query as any); @@ -1099,40 +1375,49 @@ describe('query', () => { * @ViewChildren('foo') query; * } */ - 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); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(1); - { - if (ctx.exp1) { - let rf0 = embeddedViewStart(0); + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + container(1); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(1); { - if (rf0 & RenderFlags.Create) { - firstEl = elementStart(0, 'div', null, ['foo', '']); - elementEnd(); + if (ctx.exp1) { + let rf0 = embeddedViewStart(0); + { + if (rf0 & RenderFlags.Create) { + firstEl = elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); + } + if (ctx.exp2) { + let rf1 = embeddedViewStart(1); + { + if (rf1 & RenderFlags.Create) { + lastEl = elementStart(0, 'span', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); } } - embeddedViewEnd(); + containerRefreshEnd(); } - if (ctx.exp2) { - let rf1 = embeddedViewStart(1); - { - if (rf1 & RenderFlags.Create) { - lastEl = elementStart(0, 'span', null, ['foo', '']); - elementEnd(); - } - } - embeddedViewEnd(); + }, + [], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); } - } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }); + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); const cmptInstance = renderComponent(Cmpt); const qList = (cmptInstance.query as any); @@ -1163,47 +1448,56 @@ describe('query', () => { * @ViewChildren('foo') query; * } */ - 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); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(1); - { - if (ctx.exp1) { - let rf0 = embeddedViewStart(0); + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + container(1); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(1); { - if (rf0 & RenderFlags.Create) { - firstEl = elementStart(0, 'div', null, ['foo', '']); - elementEnd(); - container(2); - } - if (rf0 & RenderFlags.Update) { - containerRefreshStart(2); + if (ctx.exp1) { + let rf0 = embeddedViewStart(0); { - if (ctx.exp2) { - let rf2 = embeddedViewStart(0); + if (rf0 & RenderFlags.Create) { + firstEl = elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + container(2); + } + if (rf0 & RenderFlags.Update) { + containerRefreshStart(2); { - if (rf2) { - lastEl = elementStart(0, 'span', null, ['foo', '']); - elementEnd(); + if (ctx.exp2) { + let rf2 = embeddedViewStart(0); + { + if (rf2) { + lastEl = elementStart(0, 'span', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); } } - embeddedViewEnd(); + containerRefreshEnd(); } } - containerRefreshEnd(); + embeddedViewEnd(); } } - embeddedViewEnd(); + containerRefreshEnd(); } - } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }); + }, + [], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); const cmptInstance = renderComponent(Cmpt); const qList = (cmptInstance.query as any); @@ -1221,44 +1515,59 @@ describe('query', () => { expect(qList.last.nativeElement).toBe(lastEl); }); + /** + * What is tested here can't be achieved in the Renderer2 as all view queries are deep by + * default and can't be marked as shallow by a user. + */ it('should support combination of deep and shallow queries', () => { /** - * + * % if (exp) { "> *
- *
+ * % } * * class Cmpt { - * @ViewChildren('foo') query; + * @ViewChildren('foo') deep; + * @ViewChildren('foo') shallow; * } */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - query(1, ['foo'], false, QUERY_READ_FROM_NODE); - container(2); - elementStart(3, 'span', null, ['foo', '']); - elementEnd(); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(2); - { - if (ctx.exp) { - let rf0 = embeddedViewStart(0); + const Cmpt = createComponent( + 'cmpt', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + container(2); + elementStart(3, 'span', null, ['foo', '']); + elementEnd(); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(2); { - if (rf0 & RenderFlags.Create) { - elementStart(0, 'div', null, ['foo', '']); - elementEnd(); + if (ctx.exp) { + let rf0 = embeddedViewStart(0); + { + if (rf0 & RenderFlags.Create) { + elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); } } - embeddedViewEnd(); + containerRefreshEnd(); } - } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.deep = tmp as QueryList); - queryRefresh(tmp = load>(1)) && (ctx.shallow = tmp as QueryList); - } - }); + }, + [], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + query(1, ['foo'], false, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.deep = tmp as QueryList); + queryRefresh(tmp = load>(1)) && + (ctx.shallow = tmp as QueryList); + } + }); const cmptInstance = renderComponent(Cmpt); const deep = (cmptInstance.deep as any); @@ -1316,15 +1625,21 @@ describe('query', () => { it('should be destroyed when the containing view is destroyed', () => { let queryInstance: QueryList; - const SimpleComponentWithQuery = - createComponent('some-component-with-query', function(rf: RenderFlags, ctx: any) { - let tmp: any; + const SimpleComponentWithQuery = createComponent( + 'some-component-with-query', + function(rf: RenderFlags, ctx: any) { if (rf & RenderFlags.Create) { - query(0, ['foo'], false, QUERY_READ_FROM_NODE); elementStart(1, 'div', null, ['foo', '']); elementEnd(); } + }, + [], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo'], false, QUERY_READ_FROM_NODE); + } if (rf & RenderFlags.Update) { + let tmp: any; queryRefresh(tmp = load>(0)) && (ctx.query = queryInstance = tmp as QueryList); } @@ -1404,19 +1719,26 @@ describe('query', () => { * @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 AppComponent = createComponent( + 'app-component', + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + elementStart(1, 'with-content'); + { element(2, 'div', null, ['foo', '']); } + elementEnd(); + element(4, 'div', ['id', 'after'], ['bar', '']); + } + }, + [WithContentComponent], [], + function(rf: RenderFlags, ctx: any) { + if (rf & RenderFlags.Create) { + query(0, ['foo', 'bar'], true, QUERY_READ_FROM_NODE); + } + if (rf & RenderFlags.Update) { + let tmp: any; + queryRefresh(tmp = load>(0)) && (ctx.foos = tmp as QueryList); + } + }); const fixture = new ComponentFixture(AppComponent); const viewQList = fixture.component.foos; diff --git a/packages/core/test/render3/render_util.ts b/packages/core/test/render3/render_util.ts index 666f8dba99..6f34b01f80 100644 --- a/packages/core/test/render3/render_util.ts +++ b/packages/core/test/render3/render_util.ts @@ -228,7 +228,8 @@ export function toHtml(componentOrElement: T | RElement): string { export function createComponent( name: string, template: ComponentTemplate, directives: DirectiveTypesOrFactory = [], - pipes: PipeTypesOrFactory = []): ComponentType { + pipes: PipeTypesOrFactory = [], + viewQuery: ComponentTemplate| null = null): ComponentType { return class Component { value: any; static ngComponentDef = defineComponent({ @@ -236,6 +237,7 @@ export function createComponent( selectors: [[name]], factory: () => new Component, template: template, + viewQuery: viewQuery, features: [PublicFeature], directives: directives, pipes: pipes