diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index a63fa40961..5d5a8d3318 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -13,7 +13,7 @@ import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_facto import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref'; import {assertNotNull} from './assert'; -import {NG_HOST_SYMBOL, createError, createLView, createTView, directiveCreate, enterView, hostElement, leaveView, locateHostElement, renderComponentOrTemplate} from './instructions'; +import {NG_HOST_SYMBOL, createError, createLView, createTView, directiveCreate, enterView, getDirectiveInstance, hostElement, leaveView, locateHostElement, renderComponentOrTemplate} from './instructions'; import {ComponentDef, ComponentType} from './interfaces/definition'; import {LElementNode} from './interfaces/node'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; @@ -178,7 +178,7 @@ export function renderComponent( // Create element node at index 0 in data array hostElement(hostNode, componentDef); // Create directive instance with n() and store at index 1 in data array (el is 0) - component = directiveCreate(1, componentDef.n(), componentDef); + component = getDirectiveInstance(directiveCreate(1, componentDef.n(), componentDef)); } finally { leaveView(oldView); } diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index a0e20cdeb4..8f33ee2013 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -18,7 +18,7 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_Vie import {Type} from '../type'; import {assertLessThan} from './assert'; -import {assertPreviousIsParent, getPreviousOrParentNode, getRenderer, renderEmbeddedTemplate} from './instructions'; +import {assertPreviousIsParent, getDirectiveInstance, getPreviousOrParentNode, getRenderer, renderEmbeddedTemplate} from './instructions'; import {ComponentTemplate, DirectiveDef} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; import {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode} from './interfaces/node'; @@ -293,7 +293,7 @@ export function getOrCreateInjectable( // and matches the given token, return the directive instance. const directiveDef = tData[i] as DirectiveDef; if (directiveDef.diPublic && directiveDef.type == token) { - return node.view.data[i]; + return getDirectiveInstance(node.view.data[i]); } } } diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 3aa08f70d1..f3b4376437 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -1185,7 +1185,7 @@ export function componentRefresh(directiveIndex: number, elementIndex: number ngDevMode && assertNodeType(element, LNodeFlags.Element); ngDevMode && assertNotEqual(element.data, null, 'isComponent'); ngDevMode && assertDataInRange(directiveIndex); - const directive = data[directiveIndex]; + const directive = getDirectiveInstance(data[directiveIndex]); const hostView = element.data !; ngDevMode && assertNotEqual(hostView, null, 'hostView'); const oldView = enterView(hostView, element); @@ -1858,6 +1858,12 @@ export function getRenderer(): Renderer3 { return renderer; } +export function getDirectiveInstance(instanceOrArray: T | [T]): T { + // Directives with content queries store an array in data[directiveIndex] + // with the instance as the first index + return Array.isArray(instanceOrArray) ? instanceOrArray[0] : instanceOrArray; +} + export function assertPreviousIsParent() { assertEqual(isParent, true, 'isParent'); } diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 91b6b43a56..b93e500e19 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -60,12 +60,15 @@ export interface DirectiveDef { readonly exportAs: string|null; /** - * factory function used to create a new directive instance. + * Factory function used to create a new directive instance. + * + * Usually returns the directive instance, but if the directive has a content query, + * it instead returns an array that contains the instance as well as content query data. * * NOTE: this property is short (1 char) because it is used in * component templates which is sensitive to size. */ - n(): T; + n(): T|[T]; /** * Refreshes host bindings on the associated directive. Also calls lifecycle hooks @@ -108,7 +111,7 @@ export interface ComponentDef extends DirectiveDef { export interface DirectiveDefArgs { type: Type; - factory: () => T; + factory: () => T | [T]; inputs?: {[P in keyof T]?: string}; outputs?: {[P in keyof T]?: string}; methods?: {[P in keyof T]?: string}; diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index d70ae75db3..1d293db2db 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -17,7 +17,7 @@ import {getSymbolIterator} from '../util'; import {assertEqual, assertNotNull} from './assert'; import {ReadFromInjectorFn, getOrCreateNodeInjectorForNode} from './di'; -import {assertPreviousIsParent, getCurrentQueries} from './instructions'; +import {assertPreviousIsParent, getCurrentQueries, memory} from './instructions'; import {DirectiveDef, unusedValueExportToPlacateAjd as unused1} from './interfaces/definition'; import {LInjector, unusedValueExportToPlacateAjd as unused2} from './interfaces/injector'; import {LContainerNode, LElementNode, LNode, LNodeFlags, TNode, unusedValueExportToPlacateAjd as unused3} from './interfaces/node'; @@ -370,13 +370,27 @@ class QueryList_/* implements viewEngine_QueryList */ { export type QueryList = viewEngine_QueryList; export const QueryList: typeof viewEngine_QueryList = QueryList_ as any; +/** + * Creates and returns a QueryList. + * + * @param memoryIndex The index in memory where the QueryList should be saved. If null, + * this is is a content query and the QueryList will be saved later through directiveCreate. + * @param predicate The type for which the query will search + * @param descend Whether or not to descend into children + * @param read What to save in the query + * @returns QueryList + */ export function query( - predicate: Type| string[], descend?: boolean, + memoryIndex: number | null, predicate: Type| string[], descend?: boolean, read?: QueryReadType| Type): QueryList { ngDevMode && assertPreviousIsParent(); const queryList = new QueryList(); const queries = getCurrentQueries(LQueries_); queries.track(queryList, predicate, descend, read); + + if (memoryIndex != null) { + memory(memoryIndex, queryList); + } return queryList; } diff --git a/packages/core/test/render3/compiler_canonical_spec.ts b/packages/core/test/render3/compiler_canonical_spec.ts index c2abd14361..462bc03343 100644 --- a/packages/core/test/render3/compiler_canonical_spec.ts +++ b/packages/core/test/render3/compiler_canonical_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Directive, Injectable, Input, NgModule, Optional, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef} from '../../src/core'; +import {Component, ContentChild, Directive, Injectable, Input, NgModule, Optional, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef} from '../../src/core'; import * as r3 from '../../src/render3/index'; import {containerEl, renderComponent, requestAnimationFrame, toHtml} from './render_util'; @@ -183,7 +183,7 @@ describe('compiler specification', () => { template: function(ctx: SimpleComponent, cm: boolean) { if (cm) { r3.pD(0); - r3.E(0, 'div'); + r3.E(1, 'div'); r3.P(2, 0); r3.e(); } @@ -272,7 +272,7 @@ describe('compiler specification', () => { template: function ViewQueryComponent_Template(ctx: ViewQueryComponent, cm: boolean) { let tmp: any; if (cm) { - r3.m(0, r3.Q(SomeDirective, false)); + r3.Q(0, SomeDirective, false); r3.E(1, 'div', null, e1_dirs); r3.e(); } @@ -289,7 +289,82 @@ describe('compiler specification', () => { const viewQueryComp = renderComponent(ViewQueryComponent); expect((viewQueryComp.someDir as QueryList).toArray()).toEqual([someDir !]); }); - + + it('should support content queries', () => { + let contentQueryComp: ContentQueryComponent; + + @Component({ + selector: 'content-query-component', + template: ` +
+ ` + }) + class ContentQueryComponent { + @ContentChild(SomeDirective) someDir: SomeDirective; + + // NORMATIVE + static ngComponentDef = r3.defineComponent({ + type: ContentQueryComponent, + tag: 'content-query-component', + factory: function ContentQueryComponent_Factory() { + return [new ContentQueryComponent(), r3.Q(null, SomeDirective, false)]; + }, + hostBindings: function ContentQueryComponent_HostBindings( + dirIndex: number, elIndex: number) { + let tmp: any; + r3.qR(tmp = r3.m(dirIndex)[1]) && (r3.m(dirIndex)[0].someDir = tmp); + }, + template: function ContentQueryComponent_Template( + ctx: ContentQueryComponent, cm: boolean) { + if (cm) { + r3.pD(0); + r3.E(1, 'div'); + r3.P(2, 0); + r3.e(); + } + } + }); + // /NORMATIVE + } + + @Component({ + selector: 'my-app', + template: ` + +
+
+ ` + }) + class MyApp { + static ngComponentDef = r3.defineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: MyApp, cm: boolean) { + if (cm) { + r3.E(0, ContentQueryComponent); + contentQueryComp = r3.m(1)[0]; + r3.E(2, 'div', null, e2_dirs); + r3.e(); + r3.e(); + } + ContentQueryComponent.ngComponentDef.h(1, 0); + SomeDirective.ngDirectiveDef.h(3, 2); + r3.r(1, 0); + r3.r(3, 2); + } + }); + } + + const e2_dirs = [SomeDirective]; + + expect(renderComp(MyApp)) + .toEqual(`
`); + expect((contentQueryComp !.someDir as QueryList).toArray()).toEqual([ + someDir ! + ]); + }); + }); }); diff --git a/packages/core/test/render3/define_spec.ts b/packages/core/test/render3/define_spec.ts index f8f48006ac..8aa5a12174 100644 --- a/packages/core/test/render3/define_spec.ts +++ b/packages/core/test/render3/define_spec.ts @@ -35,7 +35,7 @@ describe('define', () => { }); } - const myDir = MyDirective.ngDirectiveDef.n(); + const myDir = MyDirective.ngDirectiveDef.n() as MyDirective; myDir.valA = 'first'; expect(myDir.valA).toEqual('first'); myDir.valB = 'second'; diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index fdb8005bda..b4b5a3beee 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -58,8 +58,8 @@ describe('query', () => { */ let tmp: any; if (cm) { - m(0, Q(Child, false)); - m(1, Q(Child, true)); + Q(0, Child, false); + Q(1, Child, true); E(2, Child); { child1 = m(3); @@ -92,7 +92,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(Child, false, QUERY_READ_ELEMENT_REF)); + Q(0, Child, false, QUERY_READ_ELEMENT_REF); elToQuery = E(1, 'div', null, [Child]); e(); } @@ -120,7 +120,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(Child, false, OtherChild)); + Q(0, Child, false, OtherChild); E(1, 'div', null, [Child, OtherChild]); { otherChildInstance = m(3); } e(); @@ -146,7 +146,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(Child, false, OtherChild)); + Q(0, Child, false, OtherChild); E(1, 'div', null, [Child]); e(); } @@ -174,7 +174,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], false, QUERY_READ_FROM_NODE)); + Q(0, ['foo'], false, QUERY_READ_FROM_NODE); elToQuery = E(1, 'div', null, null, ['foo', '']); e(); E(2, 'div'); @@ -204,7 +204,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo', 'bar'], undefined, QUERY_READ_FROM_NODE)); + Q(0, ['foo', 'bar'], undefined, QUERY_READ_FROM_NODE); el1ToQuery = E(1, 'div', null, null, ['foo', '']); e(); E(2, 'div'); @@ -235,7 +235,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], false, QUERY_READ_ELEMENT_REF)); + Q(0, ['foo'], false, QUERY_READ_ELEMENT_REF); elToQuery = E(1, 'div', null, null, ['foo', '']); e(); E(2, 'div'); @@ -261,7 +261,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], false, QUERY_READ_CONTAINER_REF)); + Q(0, ['foo'], false, QUERY_READ_CONTAINER_REF); E(1, 'div', null, null, ['foo', '']); e(); } @@ -284,7 +284,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], false, QUERY_READ_CONTAINER_REF)); + Q(0, ['foo'], false, QUERY_READ_CONTAINER_REF); C(1, undefined, undefined, undefined, undefined, ['foo', '']); } qR(tmp = m>(0)) && (ctx.query = tmp as QueryList); @@ -307,7 +307,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], false, QUERY_READ_ELEMENT_REF)); + Q(0, ['foo'], false, QUERY_READ_ELEMENT_REF); C(1, undefined, undefined, undefined, undefined, ['foo', '']); } qR(tmp = m>(0)) && (ctx.query = tmp as QueryList); @@ -330,7 +330,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], undefined, QUERY_READ_FROM_NODE)); + Q(0, ['foo'], undefined, QUERY_READ_FROM_NODE); C(1, undefined, undefined, undefined, undefined, ['foo', '']); } qR(tmp = m>(0)) && (ctx.query = tmp as QueryList); @@ -353,7 +353,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], false, QUERY_READ_TEMPLATE_REF)); + Q(0, ['foo'], false, QUERY_READ_TEMPLATE_REF); C(1, undefined, undefined, undefined, undefined, ['foo', '']); } qR(tmp = m>(0)) && (ctx.query = tmp as QueryList); @@ -378,7 +378,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], true, QUERY_READ_FROM_NODE)); + Q(0, ['foo'], true, QUERY_READ_FROM_NODE); E(1, Child, null, null, ['foo', '']); { childInstance = m(2); } e(); @@ -406,7 +406,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], true, QUERY_READ_FROM_NODE)); + Q(0, ['foo'], true, QUERY_READ_FROM_NODE); E(1, 'div', null, [Child], ['foo', 'child']); childInstance = m(2); e(); @@ -434,7 +434,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo', 'bar'], true, QUERY_READ_FROM_NODE)); + Q(0, ['foo', 'bar'], true, QUERY_READ_FROM_NODE); E(1, 'div', null, [Child1, Child2], ['foo', 'child1', 'bar', 'child2']); { child1Instance = m(2); @@ -465,7 +465,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], undefined, QUERY_READ_ELEMENT_REF)); + Q(0, ['foo'], undefined, QUERY_READ_ELEMENT_REF); div = E(1, 'div', null, [Child], ['foo', 'child']); e(); } @@ -491,7 +491,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo', 'bar'], undefined, QUERY_READ_FROM_NODE)); + Q(0, ['foo', 'bar'], undefined, QUERY_READ_FROM_NODE); div = E(1, 'div', null, [Child], ['foo', '', 'bar', 'child']); { childInstance = m(2); } e(); @@ -518,7 +518,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], false, Child)); + Q(0, ['foo'], false, Child); E(1, 'div', null, null, ['foo', '']); e(); } @@ -547,7 +547,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], true, QUERY_READ_FROM_NODE)); + Q(0, ['foo'], true, QUERY_READ_FROM_NODE); C(1); } cR(1); @@ -597,7 +597,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], true, QUERY_READ_FROM_NODE)); + Q(0, ['foo'], true, QUERY_READ_FROM_NODE); firstEl = E(1, 'b', null, null, ['foo', '']); e(); C(2); @@ -657,7 +657,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], true, QUERY_READ_FROM_NODE)); + Q(0, ['foo'], true, QUERY_READ_FROM_NODE); C(1); } cR(1); @@ -719,7 +719,7 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], true, QUERY_READ_FROM_NODE)); + Q(0, ['foo'], true, QUERY_READ_FROM_NODE); C(1); } cR(1); @@ -783,8 +783,8 @@ describe('query', () => { const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) { let tmp: any; if (cm) { - m(0, Q(['foo'], true, QUERY_READ_FROM_NODE)); - m(1, Q(['foo'], false, QUERY_READ_FROM_NODE)); + Q(0, ['foo'], true, QUERY_READ_FROM_NODE); + Q(1, ['foo'], false, QUERY_READ_FROM_NODE); C(2); E(3, 'span', null, null, ['foo', '']); e();