feat(ivy): add support for content query (#21912)

PR Close #21912
This commit is contained in:
Kara Erickson 2018-01-30 15:37:01 -08:00 committed by Jason Aden
parent 0365592119
commit 81306c1f61
8 changed files with 138 additions and 40 deletions

View File

@ -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<T>(
// 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);
}

View File

@ -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<T>(
// and matches the given token, return the directive instance.
const directiveDef = tData[i] as DirectiveDef<any>;
if (directiveDef.diPublic && directiveDef.type == token) {
return node.view.data[i];
return getDirectiveInstance(node.view.data[i]);
}
}
}

View File

@ -1185,7 +1185,7 @@ export function componentRefresh<T>(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<T>(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<T>(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');
}

View File

@ -60,12 +60,15 @@ export interface DirectiveDef<T> {
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<T> extends DirectiveDef<T> {
export interface DirectiveDefArgs<T> {
type: Type<T>;
factory: () => T;
factory: () => T | [T];
inputs?: {[P in keyof T]?: string};
outputs?: {[P in keyof T]?: string};
methods?: {[P in keyof T]?: string};

View File

@ -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_<T>/* implements viewEngine_QueryList<T> */ {
export type QueryList<T> = viewEngine_QueryList<T>;
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<T>
*/
export function query<T>(
predicate: Type<any>| string[], descend?: boolean,
memoryIndex: number | null, predicate: Type<any>| string[], descend?: boolean,
read?: QueryReadType<T>| Type<T>): QueryList<T> {
ngDevMode && assertPreviousIsParent();
const queryList = new QueryList<T>();
const queries = getCurrentQueries(LQueries_);
queries.track(queryList, predicate, descend, read);
if (memoryIndex != null) {
memory(memoryIndex, queryList);
}
return queryList;
}

View File

@ -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<SomeDirective>).toArray()).toEqual([someDir !]);
});
it('should support content queries', () => {
let contentQueryComp: ContentQueryComponent;
@Component({
selector: 'content-query-component',
template: `
<div><ng-content></ng-content></div>
`
})
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<any[]>(dirIndex)[1]) && (r3.m<any[]>(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: `
<content-query-component>
<div someDir></div>
</content-query-component>
`
})
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<any[]>(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(`<content-query-component><div><div></div></div></content-query-component>`);
expect((contentQueryComp !.someDir as QueryList<SomeDirective>).toArray()).toEqual([
someDir !
]);
});
});
});

View File

@ -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';

View File

@ -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<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
@ -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<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
@ -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<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
@ -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<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
@ -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();