Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Canonical query examples #21912

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/core/src/render3/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/render3/di.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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]);
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/render3/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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');
}
Expand Down
9 changes: 6 additions & 3 deletions packages/core/src/render3/interfaces/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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};
Expand Down
18 changes: 16 additions & 2 deletions packages/core/src/render3/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}

Expand Down
133 changes: 131 additions & 2 deletions packages/core/test/render3/compiler_canonical_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Component, Directive, Injectable, Input, NgModule, Optional, SimpleChanges, TemplateRef, Type, 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';
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -238,6 +238,135 @@ describe('compiler specification', () => {
});
}
});

describe('queries', () => {
let someDir: SomeDirective;

@Directive({
selector: '[someDir]',
})
class SomeDirective {
static ngDirectiveDef = r3.defineDirective({
type: SomeDirective,
factory: function SomeDirective_Factory() { return someDir = new SomeDirective(); },
features: [r3.PublicFeature]
});
}

it('should support view queries', () => {
@Component({
selector: 'view-query-component',
template: `
<div someDir></div>
`
})
class ViewQueryComponent {
@ViewChild(SomeDirective) someDir: SomeDirective;


// NORMATIVE
static ngComponentDef = r3.defineComponent({
type: ViewQueryComponent,
tag: 'view-query-component',
factory: function ViewQueryComponent_Factory() { return new ViewQueryComponent(); },
template: function ViewQueryComponent_Template(ctx: ViewQueryComponent, cm: boolean) {
let tmp: any;
if (cm) {
r3.Q(0, SomeDirective, false);
r3.E(1, 'div', null, e1_dirs);
r3.e();
}
r3.qR(tmp = r3.m<QueryList<any>>(0)) && (ctx.someDir = tmp as QueryList<any>);
SomeDirective.ngDirectiveDef.h(2, 1);
r3.r(2, 1);
}
});
// /NORMATIVE
}

const e1_dirs = [SomeDirective];

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 !
]);
});

});

});

describe('local references', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/render3/define_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down