fix(core): `QueryList` should not fire changes if the underlying list did not change. (#40091)

Previous implementation would fire changes `QueryList.changes.subscribe`
whenever the `QueryList` was recomputed. This resulted in artificially
high number of change notifications, as it is possible that recomputing
`QueryList` results in the same list. When the `QueryList` gets recomputed
is an implementation detail and it should not be the thing which determines
how often change event should fire.

This change introduces a new `emitDistinctChangesOnly` option for
`ContentChildren` and `ViewChildren`.

```
export class QueryCompWithStrictChangeEmitParent {
  @ContentChildren('foo', {
    // This option will become the default in the future
    emitDistinctChangesOnly: true,
  })
  foos!: QueryList<any>;
}
```

PR Close #40091
This commit is contained in:
Misko Hevery 2020-12-10 15:10:56 -08:00 committed by Andrew Kushnir
parent cf02cf1e18
commit e32b6256ce
47 changed files with 719 additions and 227 deletions

View File

@ -174,10 +174,12 @@ export declare type ContentChildren = Query;
export declare interface ContentChildrenDecorator {
(selector: Type<any> | InjectionToken<unknown> | Function | string, opts?: {
descendants?: boolean;
emitDistinctChangesOnly?: boolean;
read?: any;
}): any;
new (selector: Type<any> | InjectionToken<unknown> | Function | string, opts?: {
descendants?: boolean;
emitDistinctChangesOnly?: boolean;
read?: any;
}): Query;
}
@ -734,6 +736,7 @@ export declare type Provider = TypeProvider | ValueProvider | ClassProvider | Co
export declare interface Query {
descendants: boolean;
emitDistinctChangesOnly: boolean;
first: boolean;
isViewQuery: boolean;
read: any;
@ -746,12 +749,12 @@ export declare abstract class Query {
export declare class QueryList<T> implements Iterable<T> {
[Symbol.iterator]: () => Iterator<T>;
readonly changes: Observable<any>;
get changes(): Observable<any>;
readonly dirty = true;
readonly first: T;
readonly last: T;
readonly length: number;
constructor();
constructor(_emitDistinctChangesOnly?: boolean);
destroy(): void;
filter(fn: (item: T, index: number, array: T[]) => boolean): T[];
find(fn: (item: T, index: number, array: T[]) => boolean): T | undefined;
@ -760,7 +763,7 @@ export declare class QueryList<T> implements Iterable<T> {
map<U>(fn: (item: T, index: number, array: T[]) => U): U[];
notifyOnChanges(): void;
reduce<U>(fn: (prevValue: U, curValue: T, curIndex: number, array: T[]) => U, init: U): U;
reset(resultsTree: Array<T | any[]>): void;
reset(resultsTree: Array<T | any[]>, identityAccessor?: (value: T) => unknown): void;
setDirty(): void;
some(fn: (value: T, index: number, array: T[]) => boolean): boolean;
toArray(): T[];
@ -1010,9 +1013,11 @@ export declare type ViewChildren = Query;
export declare interface ViewChildrenDecorator {
(selector: Type<any> | InjectionToken<unknown> | Function | string, opts?: {
read?: any;
emitDistinctChangesOnly?: boolean;
}): any;
new (selector: Type<any> | InjectionToken<unknown> | Function | string, opts?: {
read?: any;
emitDistinctChangesOnly?: boolean;
}): ViewChildren;
}

View File

@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 3033,
"main-es2015": 447349,
"main-es2015": 448090,
"polyfills-es2015": 52493
}
}
@ -21,7 +21,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 3153,
"main-es2015": 431137,
"main-es2015": 432078,
"polyfills-es2015": 52493
}
}

View File

@ -39,7 +39,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2285,
"main-es2015": 240909,
"main-es2015": 241738,
"polyfills-es2015": 36709,
"5-es2015": 745
}

View File

@ -147,6 +147,8 @@ function toQueryMetadata<TExpression>(obj: AstObject<R3DeclareQueryMetadata, TEx
first: obj.has('first') ? obj.getBoolean('first') : false,
predicate,
descendants: obj.has('descendants') ? obj.getBoolean('descendants') : false,
emitDistinctChangesOnly:
obj.has('emitDistinctChangesOnly') ? obj.getBoolean('emitDistinctChangesOnly') : true,
read: obj.has('read') ? obj.getOpaque('read') : null,
static: obj.has('static') ? obj.getBoolean('static') : false,
};

View File

@ -7,6 +7,7 @@
*/
import {compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, Expression, ExternalExpr, getSafePropertyAccessString, Identifiers, makeBindingParser, ParsedHostBindings, ParseError, parseHostBindings, R3DependencyMetadata, R3DirectiveDef, R3DirectiveMetadata, R3FactoryTarget, R3QueryMetadata, R3ResolvedDependencyType, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
import {emitDistinctChangesOnlyDefaultValue} from '@angular/compiler/src/core';
import * as ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
@ -436,6 +437,7 @@ export function extractQueryMetadata(
let read: Expression|null = null;
// The default value for descendants is true for every decorator except @ContentChildren.
let descendants: boolean = name !== 'ContentChildren';
let emitDistinctChangesOnly: boolean = emitDistinctChangesOnlyDefaultValue;
if (args.length === 2) {
const optionsExpr = unwrapExpression(args[1]);
if (!ts.isObjectLiteralExpression(optionsExpr)) {
@ -458,6 +460,17 @@ export function extractQueryMetadata(
descendants = descendantsValue;
}
if (options.has('emitDistinctChangesOnly')) {
const emitDistinctChangesOnlyExpr = options.get('emitDistinctChangesOnly')!;
const emitDistinctChangesOnlyValue = evaluator.evaluate(emitDistinctChangesOnlyExpr);
if (typeof emitDistinctChangesOnlyValue !== 'boolean') {
throw createValueHasWrongTypeError(
emitDistinctChangesOnlyExpr, emitDistinctChangesOnlyValue,
`@${name} options.emitDistinctChangesOnlys must be a boolean`);
}
emitDistinctChangesOnly = emitDistinctChangesOnlyValue;
}
if (options.has('static')) {
const staticValue = evaluator.evaluate(options.get('static')!);
if (typeof staticValue !== 'boolean') {
@ -480,6 +493,7 @@ export function extractQueryMetadata(
descendants,
read,
static: isStatic,
emitDistinctChangesOnly,
};
}

View File

@ -32,7 +32,7 @@ import * as i0 from "@angular/core";
export class ViewQueryComponent {
}
ViewQueryComponent.ɵfac = function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); };
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, descendants: true }, { propertyName: "someDirs", predicate: SomeDirective, descendants: true }], ngImport: i0, template: `
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true }, { propertyName: "someDirs", predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true }], ngImport: i0, template: `
<div someDir></div>
`, isInline: true, directives: [{ type: i0.forwardRef(function () { return SomeDirective; }), selector: "[someDir]" }] });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ViewQueryComponent, [{
@ -86,7 +86,7 @@ import * as i0 from "@angular/core";
export class ViewQueryComponent {
}
ViewQueryComponent.ɵfac = function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); };
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], descendants: true }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], descendants: true }], ngImport: i0, template: `
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], emitDistinctChangesOnly: false, descendants: true }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], emitDistinctChangesOnly: false, descendants: true }], ngImport: i0, template: `
<div #myRef></div>
<div #myRef1></div>
`, isInline: true });
@ -166,7 +166,7 @@ import * as i0 from "@angular/core";
export class ViewQueryComponent {
}
ViewQueryComponent.ɵfac = function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); };
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, descendants: true, static: true }, { propertyName: "foo", first: true, predicate: ["foo"], descendants: true }], ngImport: i0, template: `
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true, static: true }, { propertyName: "foo", first: true, predicate: ["foo"], emitDistinctChangesOnly: false, descendants: true }], ngImport: i0, template: `
<div someDir></div>
`, isInline: true, directives: [{ type: i0.forwardRef(function () { return SomeDirective; }), selector: "[someDir]" }] });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ViewQueryComponent, [{
@ -246,7 +246,7 @@ import * as i0 from "@angular/core";
export class ViewQueryComponent {
}
ViewQueryComponent.ɵfac = function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); };
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], descendants: true, read: TemplateRef }, { propertyName: "someDir", first: true, predicate: SomeDirective, descendants: true, read: ElementRef }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], descendants: true, read: ElementRef }, { propertyName: "someDirs", predicate: SomeDirective, descendants: true, read: TemplateRef }], ngImport: i0, template: `
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], emitDistinctChangesOnly: false, descendants: true, read: TemplateRef }, { propertyName: "someDir", first: true, predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true, read: ElementRef }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], emitDistinctChangesOnly: false, descendants: true, read: ElementRef }, { propertyName: "someDirs", predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true, read: TemplateRef }], ngImport: i0, template: `
<div someDir></div>
<div #myRef></div>
<div #myRef1></div>
@ -336,7 +336,7 @@ import * as i0 from "@angular/core";
export class ContentQueryComponent {
}
ContentQueryComponent.ɵfac = function ContentQueryComponent_Factory(t) { return new (t || ContentQueryComponent)(); };
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, descendants: true }, { propertyName: "someDirList", predicate: SomeDirective }], ngImport: i0, template: `
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true }, { propertyName: "someDirList", predicate: SomeDirective, emitDistinctChangesOnly: false }], ngImport: i0, template: `
<div><ng-content></ng-content></div>
`, isInline: true });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ContentQueryComponent, [{
@ -413,7 +413,7 @@ import * as i0 from "@angular/core";
export class ContentQueryComponent {
}
ContentQueryComponent.ɵfac = function ContentQueryComponent_Factory(t) { return new (t || ContentQueryComponent)(); };
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], descendants: true }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"] }], ngImport: i0, template: `
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], emitDistinctChangesOnly: false, descendants: true }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], emitDistinctChangesOnly: false }], ngImport: i0, template: `
<div #myRef></div>
<div #myRef1></div>
`, isInline: true });
@ -493,7 +493,7 @@ import * as i0 from "@angular/core";
export class ContentQueryComponent {
}
ContentQueryComponent.ɵfac = function ContentQueryComponent_Factory(t) { return new (t || ContentQueryComponent)(); };
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, descendants: true, static: true }, { propertyName: "foo", first: true, predicate: ["foo"], descendants: true }], ngImport: i0, template: `
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true, static: true }, { propertyName: "foo", first: true, predicate: ["foo"], emitDistinctChangesOnly: false, descendants: true }], ngImport: i0, template: `
<div><ng-content></ng-content></div>
`, isInline: true });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ContentQueryComponent, [{
@ -596,7 +596,7 @@ import * as i0 from "@angular/core";
export class ContentQueryComponent {
}
ContentQueryComponent.ɵfac = function ContentQueryComponent_Factory(t) { return new (t || ContentQueryComponent)(); };
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], descendants: true, read: TemplateRef }, { propertyName: "someDir", first: true, predicate: SomeDirective, descendants: true, read: ElementRef }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], read: ElementRef }, { propertyName: "someDirs", predicate: SomeDirective, read: TemplateRef }], ngImport: i0, template: `
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "myRef", first: true, predicate: ["myRef"], emitDistinctChangesOnly: false, descendants: true, read: TemplateRef }, { propertyName: "someDir", first: true, predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true, read: ElementRef }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], emitDistinctChangesOnly: false, read: ElementRef }, { propertyName: "someDirs", predicate: SomeDirective, emitDistinctChangesOnly: false, read: TemplateRef }], ngImport: i0, template: `
<div someDir></div>
<div #myRef></div>
<div #myRef1></div>
@ -652,3 +652,91 @@ export declare class MyModule {
static ɵinj: i0.ɵɵInjectorDef<MyModule>;
}
/****************************************************************************************************
* PARTIAL FILE: some.directive.js
****************************************************************************************************/
import { Directive } from '@angular/core';
import * as i0 from "@angular/core";
export class SomeDirective {
}
SomeDirective.ɵfac = function SomeDirective_Factory(t) { return new (t || SomeDirective)(); };
SomeDirective.ɵdir = i0.ɵɵngDeclareDirective({ version: "0.0.0-PLACEHOLDER", type: SomeDirective, selector: "[someDir]", ngImport: i0 });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SomeDirective, [{
type: Directive,
args: [{
selector: '[someDir]',
}]
}], null, null); })();
/****************************************************************************************************
* PARTIAL FILE: some.directive.d.ts
****************************************************************************************************/
import * as i0 from "@angular/core";
export declare class SomeDirective {
static ɵfac: i0.ɵɵFactoryDef<SomeDirective, never>;
static ɵdir: i0.ɵɵDirectiveDefWithMeta<SomeDirective, "[someDir]", never, {}, {}, never>;
}
/****************************************************************************************************
* PARTIAL FILE: query_with_emit_distinct_changes_only.js
****************************************************************************************************/
import { Component, ContentChildren, NgModule, ViewChildren } from '@angular/core';
import { SomeDirective } from './some.directive';
import * as i0 from "@angular/core";
export class ContentQueryComponent {
}
ContentQueryComponent.ɵfac = function ContentQueryComponent_Factory(t) { return new (t || ContentQueryComponent)(); };
ContentQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ContentQueryComponent, selector: "content-query-component", queries: [{ propertyName: "myRefs", predicate: ["myRef"] }, { propertyName: "oldMyRefs", predicate: ["myRef"], emitDistinctChangesOnly: false }], viewQueries: [{ propertyName: "someDirs", predicate: SomeDirective, descendants: true }, { propertyName: "oldSomeDirs", predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true }], ngImport: i0, template: `
<div someDir></div>
<div #myRef></div>
`, isInline: true });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ContentQueryComponent, [{
type: Component,
args: [{
selector: 'content-query-component',
template: `
<div someDir></div>
<div #myRef></div>
`
}]
}], null, { myRefs: [{
type: ContentChildren,
args: ['myRef', { emitDistinctChangesOnly: true }]
}], oldMyRefs: [{
type: ContentChildren,
args: ['myRef', { emitDistinctChangesOnly: false }]
}], someDirs: [{
type: ViewChildren,
args: [SomeDirective, { emitDistinctChangesOnly: true }]
}], oldSomeDirs: [{
type: ViewChildren,
args: [SomeDirective, { emitDistinctChangesOnly: false }]
}] }); })();
export class MyModule {
}
MyModule.ɵmod = i0.ɵɵdefineNgModule({ type: MyModule });
MyModule.ɵinj = i0.ɵɵdefineInjector({ factory: function MyModule_Factory(t) { return new (t || MyModule)(); } });
(function () { (typeof ngJitMode === "undefined" || ngJitMode) && i0.ɵɵsetNgModuleScope(MyModule, { declarations: [ContentQueryComponent] }); })();
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
type: NgModule,
args: [{ declarations: [ContentQueryComponent] }]
}], null, null); })();
/****************************************************************************************************
* PARTIAL FILE: query_with_emit_distinct_changes_only.d.ts
****************************************************************************************************/
import { ElementRef, QueryList } from '@angular/core';
import * as i0 from "@angular/core";
export declare class ContentQueryComponent {
myRefs: QueryList<ElementRef>;
oldMyRefs: QueryList<ElementRef>;
someDirs: QueryList<any>;
oldSomeDirs: QueryList<any>;
static ɵfac: i0.ɵɵFactoryDef<ContentQueryComponent, never>;
static ɵcmp: i0.ɵɵComponentDefWithMeta<ContentQueryComponent, "content-query-component", never, {}, {}, ["myRefs", "oldMyRefs"], never>;
}
export declare class MyModule {
static ɵmod: i0.ɵɵNgModuleDefWithMeta<MyModule, [typeof ContentQueryComponent], never, never>;
static ɵinj: i0.ɵɵInjectorDef<MyModule>;
}

View File

@ -118,6 +118,21 @@
]
}
]
},
{
"description": "should support query emitDistinctChangesOnly flag",
"inputFiles": [
"some.directive.ts",
"query_with_emit_distinct_changes_only.ts"
],
"expectations": [
{
"failureMessage": "Invalid ContentQuery declaration",
"files": [
"query_with_emit_distinct_changes_only.js"
]
}
]
}
]
}

View File

@ -3,8 +3,8 @@ ContentQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
selectors: [["content-query-component"]],
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, true);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, false);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 1);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 0);
}
if (rf & 2) {
let $tmp$;

View File

@ -5,8 +5,8 @@ ContentQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
// ...
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, true);
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, false);
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, 1);
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 0);
}
if (rf & 2) {
let $tmp$;

View File

@ -5,10 +5,10 @@ ContentQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
// ...
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, true, TemplateRef);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, true, ElementRef);
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, false, ElementRef);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, false, TemplateRef);
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, 1, TemplateRef);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 1, ElementRef);
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 0, ElementRef);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 0, TemplateRef);
}
if (rf & 2) {
let $tmp$;

View File

@ -0,0 +1,29 @@
const $e0_attrs$ = ["myRef"];
// ...
ContentQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
// ...
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, __QueryFlags.emitDistinctChangesOnly__);
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, __QueryFlags.none__);
}
if (rf & 2) {
let $tmp$;
$r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.myRefs = $tmp$);
$r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.oldMyRefs = $tmp$);
}
},
// ...
viewQuery: function ContentQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵviewQuery(SomeDirective, __QueryFlags.emitDistinctChangesOnly__|__QueryFlags.descendants__);
$r3$.ɵɵviewQuery(SomeDirective, __QueryFlags.descendants__);
}
if (rf & 2) {
let $tmp$;
$r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.someDirs = $tmp$);
$r3$.ɵɵqueryRefresh($tmp$ = $r3$.ɵɵloadQuery()) && (ctx.oldSomeDirs = $tmp$);
}
},
//...
});

View File

@ -0,0 +1,21 @@
import {Component, ContentChildren, ElementRef, NgModule, QueryList, TemplateRef, ViewChildren} from '@angular/core';
import {SomeDirective} from './some.directive';
@Component({
selector: 'content-query-component',
template: `
<div someDir></div>
<div #myRef></div>
`
})
export class ContentQueryComponent {
@ContentChildren('myRef', {emitDistinctChangesOnly: true}) myRefs!: QueryList<ElementRef>;
@ContentChildren('myRef', {emitDistinctChangesOnly: false}) oldMyRefs!: QueryList<ElementRef>;
@ViewChildren(SomeDirective, {emitDistinctChangesOnly: true}) someDirs!: QueryList<any>;
@ViewChildren(SomeDirective, {emitDistinctChangesOnly: false}) oldSomeDirs!: QueryList<any>;
}
@NgModule({declarations: [ContentQueryComponent]})
export class MyModule {
}

View File

@ -3,8 +3,8 @@ ContentQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
selectors: [["content-query-component"]],
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵɵstaticContentQuery(dirIndex, SomeDirective, true);
$r3$.ɵɵcontentQuery(dirIndex, $ref0$, true);
$r3$.ɵɵstaticContentQuery(dirIndex, SomeDirective, 3);
$r3$.ɵɵcontentQuery(dirIndex, $ref0$, 1);
}
if (rf & 2) {
let $tmp$;

View File

@ -5,8 +5,8 @@ ViewQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
selectors: [["view-query-component"]],
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵstaticViewQuery(SomeDirective, true);
$r3$.ɵɵviewQuery($refs$, true);
$r3$.ɵɵstaticViewQuery(SomeDirective, 3);
$r3$.ɵɵviewQuery($refs$, 1);
}
if (rf & 2) {
let $tmp$;

View File

@ -3,8 +3,8 @@ ViewQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
selectors: [["view-query-component"]],
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵviewQuery(SomeDirective, true);
$r3$.ɵɵviewQuery(SomeDirective, true);
$r3$.ɵɵviewQuery(SomeDirective, 1);
$r3$.ɵɵviewQuery(SomeDirective, 1);
}
if (rf & 2) {
let $tmp$;

View File

@ -5,8 +5,8 @@ ViewQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
// ...
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵviewQuery($e0_attrs$, true);
$r3$.ɵɵviewQuery($e1_attrs$, true);
$r3$.ɵɵviewQuery($e0_attrs$, 1);
$r3$.ɵɵviewQuery($e1_attrs$, 1);
}
if (rf & 2) {
let $tmp$;

View File

@ -5,10 +5,10 @@ ViewQueryComponent.ɵcmp = $r3$.ɵɵdefineComponent({
// ...
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵviewQuery($e0_attrs$, true, TemplateRef);
$r3$.ɵɵviewQuery(SomeDirective, true, ElementRef);
$r3$.ɵɵviewQuery($e1_attrs$, true, ElementRef);
$r3$.ɵɵviewQuery(SomeDirective, true, TemplateRef);
$r3$.ɵɵviewQuery($e0_attrs$, 1, TemplateRef);
$r3$.ɵɵviewQuery(SomeDirective, 1, ElementRef);
$r3$.ɵɵviewQuery($e1_attrs$, 1, ElementRef);
$r3$.ɵɵviewQuery(SomeDirective, 1, TemplateRef);
}
if (rf & 2) {
let $tmp$;

View File

@ -1636,8 +1636,8 @@ describe('compiler compliance', () => {
selectors: [["view-query-component"]],
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵviewQuery(SomeDirective, true);
$r3$.ɵɵviewQuery(SomeDirective, true);
$r3$.ɵɵviewQuery(SomeDirective, 1);
$r3$.ɵɵviewQuery(SomeDirective, 1);
}
if (rf & 2) {
let $tmp$;
@ -1695,8 +1695,8 @@ describe('compiler compliance', () => {
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵviewQuery($e0_attrs$, true);
$r3$.ɵɵviewQuery($e1_attrs$, true);
$r3$.ɵɵviewQuery($e0_attrs$, 1);
$r3$.ɵɵviewQuery($e1_attrs$, 1);
}
if (rf & 2) {
let $tmp$;
@ -1746,8 +1746,8 @@ describe('compiler compliance', () => {
selectors: [["view-query-component"]],
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵstaticViewQuery(SomeDirective, true);
$r3$.ɵɵviewQuery($refs$, true);
$r3$.ɵɵstaticViewQuery(SomeDirective, 3);
$r3$.ɵɵviewQuery($refs$, 1);
}
if (rf & 2) {
let $tmp$;
@ -1810,10 +1810,10 @@ describe('compiler compliance', () => {
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵviewQuery($e0_attrs$, true, TemplateRef);
$r3$.ɵɵviewQuery(SomeDirective, true, ElementRef);
$r3$.ɵɵviewQuery($e1_attrs$, true, ElementRef);
$r3$.ɵɵviewQuery(SomeDirective, true, TemplateRef);
$r3$.ɵɵviewQuery($e0_attrs$, 1, TemplateRef);
$r3$.ɵɵviewQuery(SomeDirective, 1, ElementRef);
$r3$.ɵɵviewQuery($e1_attrs$, 1, ElementRef);
$r3$.ɵɵviewQuery(SomeDirective, 1, TemplateRef);
}
if (rf & 2) {
let $tmp$;
@ -1873,8 +1873,8 @@ describe('compiler compliance', () => {
selectors: [["content-query-component"]],
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, true);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, false);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 1);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 0);
}
if (rf & 2) {
let $tmp$;
@ -1933,8 +1933,8 @@ describe('compiler compliance', () => {
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, true);
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, false);
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, 1);
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 0);
}
if (rf & 2) {
let $tmp$;
@ -1992,8 +1992,8 @@ describe('compiler compliance', () => {
selectors: [["content-query-component"]],
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵɵstaticContentQuery(dirIndex, SomeDirective, true);
$r3$.ɵɵcontentQuery(dirIndex, $ref0$, true);
$r3$.ɵɵstaticContentQuery(dirIndex, SomeDirective, 3);
$r3$.ɵɵcontentQuery(dirIndex, $ref0$, 1);
}
if (rf & 2) {
let $tmp$;
@ -2057,10 +2057,10 @@ describe('compiler compliance', () => {
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, true, TemplateRef);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, true, ElementRef);
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, false, ElementRef);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, false, TemplateRef);
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, 1, TemplateRef);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 1, ElementRef);
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 0, ElementRef);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 0, TemplateRef);
}
if (rf & 2) {
let $tmp$;

View File

@ -22,14 +22,14 @@ const trim = (input: string): string => input.replace(/\s+/g, ' ').trim();
const varRegExp = (name: string): RegExp => new RegExp(`var \\w+ = \\[\"${name}\"\\];`);
const viewQueryRegExp = (predicate: string, descend: boolean, ref?: string): RegExp => {
const viewQueryRegExp = (predicate: string, flags: number, ref?: string): RegExp => {
const maybeRef = ref ? `, ${ref}` : ``;
return new RegExp(`i0\\.ɵɵviewQuery\\(${predicate}, ${descend}${maybeRef}\\)`);
return new RegExp(`i0\\.ɵɵviewQuery\\(${predicate}, ${flags}${maybeRef}\\)`);
};
const contentQueryRegExp = (predicate: string, descend: boolean, ref?: string): RegExp => {
const contentQueryRegExp = (predicate: string, flags: number, ref?: string): RegExp => {
const maybeRef = ref ? `, ${ref}` : ``;
return new RegExp(`i0\\.ɵɵcontentQuery\\(dirIndex, ${predicate}, ${descend}${maybeRef}\\)`);
return new RegExp(`i0\\.ɵɵcontentQuery\\(dirIndex, ${predicate}, ${flags}${maybeRef}\\)`);
};
const setClassMetadataRegExp = (expectedType: string): RegExp =>
@ -3093,10 +3093,10 @@ runInEachFileSystem(os => {
expect(jsContents).toMatch(varRegExp('test1'));
expect(jsContents).toMatch(varRegExp('test2'));
expect(jsContents).toMatch(varRegExp('accessor'));
// match `i0.ɵɵcontentQuery(dirIndex, _c1, true, TemplateRef)`
expect(jsContents).toMatch(contentQueryRegExp('\\w+', true, 'TemplateRef'));
// match `i0.ɵɵviewQuery(_c2, true, null)`
expect(jsContents).toMatch(viewQueryRegExp('\\w+', true));
// match `i0.ɵɵcontentQuery(dirIndex, _c1, 1, TemplateRef)`
expect(jsContents).toMatch(contentQueryRegExp('\\w+', 1, 'TemplateRef'));
// match `i0.ɵɵviewQuery(_c2, 1, null)`
expect(jsContents).toMatch(viewQueryRegExp('\\w+', 1));
});
it('should generate queries for directives', () => {
@ -3125,14 +3125,14 @@ runInEachFileSystem(os => {
expect(jsContents).toMatch(varRegExp('test1'));
expect(jsContents).toMatch(varRegExp('test2'));
expect(jsContents).toMatch(varRegExp('accessor'));
// match `i0.ɵɵcontentQuery(dirIndex, _c1, true, TemplateRef)`
expect(jsContents).toMatch(contentQueryRegExp('\\w+', true, 'TemplateRef'));
// match `i0.ɵɵcontentQuery(dirIndex, _c1, 1, TemplateRef)`
expect(jsContents).toMatch(contentQueryRegExp('\\w+', 1, 'TemplateRef'));
// match `i0.ɵɵviewQuery(_c2, true)`
// match `i0.ɵɵviewQuery(_c2, 1)`
// Note that while ViewQuery doesn't necessarily make sense on a directive,
// because it doesn't have a view, we still need to handle it because a component
// could extend the directive.
expect(jsContents).toMatch(viewQueryRegExp('\\w+', true));
expect(jsContents).toMatch(viewQueryRegExp('\\w+', 1));
});
it('should handle queries that use forwardRef', () => {
@ -3154,13 +3154,13 @@ runInEachFileSystem(os => {
env.driveMain();
const jsContents = env.getContents('test.js');
// match `i0.ɵɵcontentQuery(dirIndex, TemplateRef, true, null)`
expect(jsContents).toMatch(contentQueryRegExp('TemplateRef', true));
// match `i0.ɵɵcontentQuery(dirIndex, ViewContainerRef, true, null)`
expect(jsContents).toMatch(contentQueryRegExp('ViewContainerRef', true));
// match `i0.ɵɵcontentQuery(dirIndex, _c0, true, null)`
// match `i0.ɵɵcontentQuery(dirIndex, TemplateRef, 1, null)`
expect(jsContents).toMatch(contentQueryRegExp('TemplateRef', 1));
// match `i0.ɵɵcontentQuery(dirIndex, ViewContainerRef, 1, null)`
expect(jsContents).toMatch(contentQueryRegExp('ViewContainerRef', 1));
// match `i0.ɵɵcontentQuery(dirIndex, _c0, 1, null)`
expect(jsContents).toContain('_c0 = ["parens"];');
expect(jsContents).toMatch(contentQueryRegExp('_c0', true));
expect(jsContents).toMatch(contentQueryRegExp('_c0', 1));
});
it('should handle queries that use an InjectionToken', () => {
@ -3181,10 +3181,10 @@ runInEachFileSystem(os => {
env.driveMain();
const jsContents = env.getContents('test.js');
// match `i0.ɵɵviewQuery(TOKEN, true, null)`
expect(jsContents).toMatch(viewQueryRegExp('TOKEN', true));
// match `i0.ɵɵcontentQuery(dirIndex, TOKEN, true, null)`
expect(jsContents).toMatch(contentQueryRegExp('TOKEN', true));
// match `i0.ɵɵviewQuery(TOKEN, 1, null)`
expect(jsContents).toMatch(viewQueryRegExp('TOKEN', 1));
// match `i0.ɵɵcontentQuery(dirIndex, TOKEN, 1, null)`
expect(jsContents).toMatch(contentQueryRegExp('TOKEN', 1));
});
it('should compile expressions that write keys', () => {

View File

@ -169,6 +169,7 @@ export interface CompileQueryMetadata {
propertyName: string;
read: CompileTokenMetadata;
static?: boolean;
emitDistinctChangesOnly?: boolean;
}
/**

View File

@ -240,6 +240,7 @@ export interface R3QueryMetadataFacade {
first: boolean;
predicate: any|string[];
descendants: boolean;
emitDistinctChangesOnly: boolean;
read: any|null;
static: boolean;
}
@ -251,6 +252,7 @@ export interface R3DeclareQueryMetadataFacade {
descendants?: boolean;
read?: OpaqueValue;
static?: boolean;
emitDistinctChangesOnly?: boolean;
}
export interface ParseSourceSpan {

View File

@ -27,6 +27,12 @@ export interface Attribute {
export const createAttribute =
makeMetadataFactory<Attribute>('Attribute', (attributeName: string) => ({attributeName}));
// Stores the default value of `emitDistinctChangesOnly` when the `emitDistinctChangesOnly` is not
// explicitly set. This value will be changed to `true` in v12.
// TODO(misko): switch the default in v12 to `true`. See: packages/core/src/metadata/di.ts
export const emitDistinctChangesOnlyDefaultValue = false;
export interface Query {
descendants: boolean;
first: boolean;
@ -37,17 +43,27 @@ export interface Query {
}
export const createContentChildren = makeMetadataFactory<Query>(
'ContentChildren',
(selector?: any, data: any = {}) =>
({selector, first: false, isViewQuery: false, descendants: false, ...data}));
'ContentChildren', (selector?: any, data: any = {}) => ({
selector,
first: false,
isViewQuery: false,
descendants: false,
emitDistinctChangesOnly: emitDistinctChangesOnlyDefaultValue,
...data
}));
export const createContentChild = makeMetadataFactory<Query>(
'ContentChild',
(selector?: any, data: any = {}) =>
({selector, first: true, isViewQuery: false, descendants: true, ...data}));
export const createViewChildren = makeMetadataFactory<Query>(
'ViewChildren',
(selector?: any, data: any = {}) =>
({selector, first: false, isViewQuery: true, descendants: true, ...data}));
'ViewChildren', (selector?: any, data: any = {}) => ({
selector,
first: false,
isViewQuery: true,
descendants: true,
emitDistinctChangesOnly: emitDistinctChangesOnlyDefaultValue,
...data
}));
export const createViewChild = makeMetadataFactory<Query>(
'ViewChild',
(selector: any, data: any) =>
@ -224,6 +240,7 @@ export const enum NodeFlags {
StaticQuery = 1 << 28,
DynamicQuery = 1 << 29,
TypeModuleProvider = 1 << 30,
EmitDistinctChangesOnly = 1 << 31,
CatQuery = TypeContentQuery | TypeViewQuery,
// mutually exclusive values...

View File

@ -243,7 +243,8 @@ function convertToR3QueryMetadata(facade: R3QueryMetadataFacade): R3QueryMetadat
predicate: Array.isArray(facade.predicate) ? facade.predicate :
new WrappedNodeExpr(facade.predicate),
read: facade.read ? new WrappedNodeExpr(facade.read) : null,
static: facade.static
static: facade.static,
emitDistinctChangesOnly: facade.emitDistinctChangesOnly,
};
}
@ -257,6 +258,7 @@ function convertQueryDeclarationToMetadata(declaration: R3DeclareQueryMetadataFa
descendants: declaration.descendants ?? false,
read: declaration.read ? new WrappedNodeExpr(declaration.read) : null,
static: declaration.static ?? false,
emitDistinctChangesOnly: declaration.emitDistinctChangesOnly ?? true,
};
}

View File

@ -1196,6 +1196,7 @@ export class CompileMetadataResolver {
selectors,
first: q.first,
descendants: q.descendants,
emitDistinctChangesOnly: q.emitDistinctChangesOnly,
propertyName,
read: q.read ? this._getTokenMetadata(q.read) : null!,
static: q.static

View File

@ -232,6 +232,14 @@ export interface R3DeclareQueryMetadata {
*/
descendants?: boolean;
/**
* True to only fire changes if there are underlying changes to the query.
*/
// TODO(misko): This will become `true` be default in v12. `QueryList.changes` would fire even if
// no changes to the query list were detected. This is not ideal, as changes should only fire if
// the `QueryList` actually materially changed.
emitDistinctChangesOnly?: boolean;
/**
* An expression representing a type to read from each matched node, or null if the default value
* for a given node is to be returned.

View File

@ -86,6 +86,11 @@ function compileQuery(query: R3QueryMetadata): o.LiteralMapExpr {
}
meta.set(
'predicate', Array.isArray(query.predicate) ? asLiteral(query.predicate) : query.predicate);
if (!query.emitDistinctChangesOnly) {
// `emitDistinctChangesOnly` is special because in future we expect it to be `true`. For this
// reason the absence should be interpreted as `true`.
meta.set('emitDistinctChangesOnly', o.literal(false));
}
if (query.descendants) {
meta.set('descendants', o.literal(true));
}

View File

@ -304,6 +304,13 @@ export interface R3QueryMetadata {
*/
descendants: boolean;
/**
* If the `QueryList` should fire change event only if actual change to query was computed (vs old
* behavior where the change was fired whenever the query was recomputed, even if the recomputed
* query resulted in the same list.)
*/
emitDistinctChangesOnly: boolean;
/**
* An expression representing a type to read from each matched node, or null if the default value
* for a given node is to be returned.

View File

@ -404,6 +404,7 @@ function queriesFromGlobalMetadata(
predicate: selectorsFromGlobalMetadata(query.selectors, outputCtx),
descendants: query.descendants,
read,
emitDistinctChangesOnly: !!query.emitDistinctChangesOnly,
static: !!query.static
};
});
@ -435,13 +436,55 @@ function selectorsFromGlobalMetadata(
}
function prepareQueryParams(query: R3QueryMetadata, constantPool: ConstantPool): o.Expression[] {
const parameters = [getQueryPredicate(query, constantPool), o.literal(query.descendants)];
const parameters = [getQueryPredicate(query, constantPool), o.literal(toQueryFlags(query))];
if (query.read) {
parameters.push(query.read);
}
return parameters;
}
/**
* A set of flags to be used with Queries.
*
* NOTE: Ensure changes here are in sync with `packages/core/src/render3/interfaces/query.ts`
*/
export const enum QueryFlags {
/**
* No flags
*/
none = 0b0000,
/**
* Whether or not the query should descend into children.
*/
descendants = 0b0001,
/**
* The query can be computed statically and hence can be assigned eagerly.
*
* NOTE: Backwards compatibility with ViewEngine.
*/
isStatic = 0b0010,
/**
* If the `QueryList` should fire change event only if actual change to query was computed (vs old
* behavior where the change was fired whenever the query was recomputed, even if the recomputed
* query resulted in the same list.)
*/
emitDistinctChangesOnly = 0b0100,
}
/**
* Translates query flags into `TQueryFlags` type in packages/core/src/render3/interfaces/query.ts
* @param query
*/
function toQueryFlags(query: R3QueryMetadata): number {
// NOTE: Verify that changes here match
return (query.descendants ? 1 /* TQueryFlags.descendants */ : 0) |
(query.static ? 2 /* TQueryFlags.isStatic */ : 0) |
(query.emitDistinctChangesOnly ? 4 /* TQueryFlags.emitDistinctChangesOnly */ : 0);
}
function convertAttributesToExpressions(attributes: {[name: string]: o.Expression}):
o.Expression[] {
const values: o.Expression[] = [];

View File

@ -143,7 +143,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
// Note: queries start with id 1 so we can use the number in a Bloom filter!
const queryId = queryIndex + 1;
const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All;
const flags = NodeFlags.TypeViewQuery | calcStaticDynamicQueryFlags(query);
const flags = NodeFlags.TypeViewQuery | calcQueryFlags(query);
this.nodes.push(() => ({
sourceSpan: null,
nodeFlags: flags,
@ -485,7 +485,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
dirAst.directive.queries.forEach((query, queryIndex) => {
const queryId = dirAst.contentQueryStartId + queryIndex;
const flags = NodeFlags.TypeContentQuery | calcStaticDynamicQueryFlags(query);
const flags = NodeFlags.TypeContentQuery | calcQueryFlags(query);
const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All;
this.nodes.push(() => ({
sourceSpan: dirAst.sourceSpan,
@ -1028,7 +1028,7 @@ function elementEventNameAndTarget(
}
}
function calcStaticDynamicQueryFlags(query: CompileQueryMetadata) {
function calcQueryFlags(query: CompileQueryMetadata) {
let flags = NodeFlags.None;
// Note: We only make queries static that query for a single item and the user specifically
// set the to be static. This is because of backwards compatibility with the old view compiler...
@ -1037,6 +1037,9 @@ function calcStaticDynamicQueryFlags(query: CompileQueryMetadata) {
} else {
flags |= NodeFlags.DynamicQuery;
}
if (query.emitDistinctChangesOnly) {
flags |= NodeFlags.EmitDistinctChangesOnly;
}
return flags;
}

View File

@ -240,6 +240,7 @@ export interface R3QueryMetadataFacade {
first: boolean;
predicate: any|string[];
descendants: boolean;
emitDistinctChangesOnly: boolean;
read: any|null;
static: boolean;
}
@ -251,6 +252,7 @@ export interface R3DeclareQueryMetadataFacade {
descendants?: boolean;
read?: OpaqueValue;
static?: boolean;
emitDistinctChangesOnly?: boolean;
}
export interface ParseSourceSpan {

View File

@ -86,3 +86,14 @@ export class ElementRef<T = any> {
*/
static __NG_ELEMENT_ID__: () => ElementRef = SWITCH_ELEMENT_REF_FACTORY;
}
/**
* Unwraps `ElementRef` and return the `nativeElement`.
*
* Conditionally unwrap the `ElementRef`.
* @param value value to unwrap
* @returns `nativeElement` if `ElementRef` otherwise returns value as is.
*/
export function unwrapElementRef<T, R>(value: T|ElementRef<R>): T|R {
return value instanceof ElementRef ? value.nativeElement : value;
}

View File

@ -9,7 +9,7 @@
import {Observable} from 'rxjs';
import {EventEmitter} from '../event_emitter';
import {flatten} from '../util/array_utils';
import {arrayEquals, flatten} from '../util/array_utils';
import {getSymbolIterator} from '../util/symbol';
function symbolIterator<T>(this: QueryList<T>): Iterator<T> {
@ -45,15 +45,31 @@ function symbolIterator<T>(this: QueryList<T>): Iterator<T> {
export class QueryList<T> implements Iterable<T> {
public readonly dirty = true;
private _results: Array<T> = [];
public readonly changes: Observable<any> = new EventEmitter();
private _changesDetected: boolean = false;
private _changes: EventEmitter<QueryList<T>>|null = null;
readonly length: number = 0;
// TODO(issue/24571): remove '!'.
readonly first!: T;
// TODO(issue/24571): remove '!'.
readonly last!: T;
readonly first: T = undefined!;
readonly last: T = undefined!;
constructor() {
/**
* Returns `Observable` of `QueryList` notifying the subscriber of changes.
*
* NOTE: This currently points to `changesDeprecated` which incorrectly notifies of changes even
* if no changes to `QueryList` have occurred. (It fires more often than it needs to.)
* The implementation will change to point `changesStrict` starting with v12.
*/
get changes(): Observable<any> {
return this._changes || (this._changes = new EventEmitter());
}
/**
* @param emitDistinctChangesOnly Whether `QueryList.changes` should fire only when actual change
* has occurred. Or if it should fire when query is recomputed. (recomputing could resolve in
* the same result) This is set to `false` for backwards compatibility but will be changed to
* true in v12.
*/
constructor(private _emitDistinctChangesOnly: boolean = false) {
// This function should be declared on the prototype, but doing so there will cause the class
// declaration to have side-effects and become not tree-shakable. For this reason we do it in
// the constructor.
@ -135,20 +151,27 @@ export class QueryList<T> implements Iterable<T> {
* occurs.
*
* @param resultsTree The query results to store
* @param identityAccessor Optional functions for extracting stable object identity from a value
* in the array.
*/
reset(resultsTree: Array<T|any[]>): void {
this._results = flatten(resultsTree);
(this as {dirty: boolean}).dirty = false;
(this as {length: number}).length = this._results.length;
(this as {last: T}).last = this._results[this.length - 1];
(this as {first: T}).first = this._results[0];
reset(resultsTree: Array<T|any[]>, identityAccessor?: (value: T) => unknown): void {
const self = this as QueryListInternal<T>;
(self as {dirty: boolean}).dirty = false;
const newResultFlat = flatten(resultsTree);
if (this._changesDetected = !arrayEquals(self._results, newResultFlat, identityAccessor)) {
self._results = newResultFlat;
self.length = newResultFlat.length;
self.last = newResultFlat[this.length - 1];
self.first = newResultFlat[0];
}
}
/**
* Triggers a change event by emitting on the `changes` {@link EventEmitter}.
*/
notifyOnChanges(): void {
(this.changes as EventEmitter<any>).emit(this);
if (this._changes && (this._emitDistinctChangesOnly ? this._changesDetected : true))
this._changes.emit(this);
}
/** internal */
@ -169,3 +192,14 @@ export class QueryList<T> implements Iterable<T> {
// over QueryLists to work correctly, since QueryList must be assignable to NgIterable.
[Symbol.iterator]!: () => Iterator<T>;
}
/**
* Internal set of APIs used by the framework. (not to be made public)
*/
export interface QueryListInternal<T> extends QueryList<T> {
reset(a: any[]): void;
notifyOnChanges(): void;
length: number;
last: T;
first: T;
}

View File

@ -98,6 +98,7 @@ export interface Attribute {
*/
export interface Query {
descendants: boolean;
emitDistinctChangesOnly: boolean;
first: boolean;
read: any;
isViewQuery: boolean;
@ -105,6 +106,12 @@ export interface Query {
static?: boolean;
}
// Stores the default value of `emitDistinctChangesOnly` when the `emitDistinctChangesOnly` is not
// explicitly set. This value will be changed to `true` in v12.
// TODO(misko): switch the default in v12 to `true`. See: packages/compiler/src/core.ts
export const emitDistinctChangesOnlyDefaultValue = false;
/**
* Base class for query metadata.
*
@ -140,6 +147,9 @@ export interface ContentChildrenDecorator {
*
* * **selector** - The directive type or the name used for querying.
* * **descendants** - True to include all descendants, otherwise include only direct children.
* * **emitDistinctChangesOnly** - The ` QueryList#changes` observable will emit new values only
* if the QueryList result has changed. The default value will change from `false` to `true` in
* v12. When `false` the `changes` observable might emit even if the QueryList has not changed.
* * **read** - Used to read a different token from the queried elements.
*
* @usageNotes
@ -157,10 +167,13 @@ export interface ContentChildrenDecorator {
*
* @Annotation
*/
(selector: Type<any>|InjectionToken<unknown>|Function|string,
opts?: {descendants?: boolean, read?: any}): any;
(selector: Type<any>|InjectionToken<unknown>|Function|string, opts?: {
descendants?: boolean,
emitDistinctChangesOnly?: boolean,
read?: any,
}): any;
new(selector: Type<any>|InjectionToken<unknown>|Function|string,
opts?: {descendants?: boolean, read?: any}): Query;
opts?: {descendants?: boolean, emitDistinctChangesOnly?: boolean, read?: any}): Query;
}
/**
@ -180,9 +193,14 @@ export type ContentChildren = Query;
* @publicApi
*/
export const ContentChildren: ContentChildrenDecorator = makePropDecorator(
'ContentChildren',
(selector?: any, data: any = {}) =>
({selector, first: false, isViewQuery: false, descendants: false, ...data}),
'ContentChildren', (selector?: any, data: any = {}) => ({
selector,
first: false,
isViewQuery: false,
descendants: false,
emitDistinctChangesOnly: emitDistinctChangesOnlyDefaultValue,
...data
}),
Query);
/**
@ -268,6 +286,9 @@ export interface ViewChildrenDecorator {
*
* * **selector** - The directive type or the name used for querying.
* * **read** - Used to read a different token from the queried elements.
* * **emitDistinctChangesOnly** - The ` QueryList#changes` observable will emit new values only
* if the QueryList result has changed. The default value will change from `false` to `true` in
* v12. When `false` the `changes` observable might emit even if the QueryList has not changed.
*
* @usageNotes
*
@ -279,9 +300,10 @@ export interface ViewChildrenDecorator {
*
* @Annotation
*/
(selector: Type<any>|InjectionToken<unknown>|Function|string, opts?: {read?: any}): any;
(selector: Type<any>|InjectionToken<unknown>|Function|string,
opts?: {read?: any, emitDistinctChangesOnly?: boolean}): any;
new(selector: Type<any>|InjectionToken<unknown>|Function|string,
opts?: {read?: any}): ViewChildren;
opts?: {read?: any, emitDistinctChangesOnly?: boolean}): ViewChildren;
}
/**
@ -298,9 +320,14 @@ export type ViewChildren = Query;
* @publicApi
*/
export const ViewChildren: ViewChildrenDecorator = makePropDecorator(
'ViewChildren',
(selector?: any, data: any = {}) =>
({selector, first: false, isViewQuery: true, descendants: true, ...data}),
'ViewChildren', (selector?: any, data: any = {}) => ({
selector,
first: false,
isViewQuery: true,
descendants: true,
emitDistinctChangesOnly: emitDistinctChangesOnlyDefaultValue,
...data
}),
Query);
/**

View File

@ -18,9 +18,39 @@ import {TView} from './view';
*/
export interface TQueryMetadata {
predicate: Type<any>|InjectionToken<unknown>|string[];
descendants: boolean;
read: any;
isStatic: boolean;
flags: QueryFlags;
}
/**
* A set of flags to be used with Queries.
*
* NOTE: Ensure changes here are reflected in `packages/compiler/src/render3/view/compiler.ts`
*/
export const enum QueryFlags {
/**
* No flags
*/
none = 0b0000,
/**
* Whether or not the query should descend into children.
*/
descendants = 0b0001,
/**
* The query can be computed statically and hence can be assigned eagerly.
*
* NOTE: Backwards compatibility with ViewEngine.
*/
isStatic = 0b0010,
/**
* If the `QueryList` should fire change event only if actual change to query was computed (vs old
* behavior where the change was fired whenever the query was recomputed, even if the recomputed
* query resulted in the same list.)
*/
emitDistinctChangesOnly = 0b0100,
}
/**

View File

@ -286,7 +286,8 @@ export function convertToR3QueryMetadata(propertyName: string, ann: Query): R3Qu
descendants: ann.descendants,
first: ann.first,
read: ann.read ? ann.read : null,
static: !!ann.static
static: !!ann.static,
emitDistinctChangesOnly: !!ann.emitDistinctChangesOnly,
};
}
function extractQueriesMetadata(

View File

@ -11,11 +11,11 @@
import {InjectionToken} from '../di/injection_token';
import {Type} from '../interface/type';
import {createElementRef, ElementRef as ViewEngine_ElementRef} from '../linker/element_ref';
import {createElementRef, ElementRef as ViewEngine_ElementRef, unwrapElementRef} from '../linker/element_ref';
import {QueryList} from '../linker/query_list';
import {createTemplateRef, TemplateRef as ViewEngine_TemplateRef} from '../linker/template_ref';
import {createContainerRef, ViewContainerRef} from '../linker/view_container_ref';
import {assertDefined, assertIndexInRange, throwError} from '../util/assert';
import {assertDefined, assertIndexInRange, assertNumber, throwError} from '../util/assert';
import {stringify} from '../util/stringify';
import {assertFirstCreatePass, assertLContainer} from './assert';
import {getNodeInjectable, locateDirectiveOrProvider} from './di';
@ -24,7 +24,7 @@ import {CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS} from './interfaces/con
import {unusedValueExportToPlacateAjd as unused1} from './interfaces/definition';
import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
import {LQueries, LQuery, TQueries, TQuery, TQueryMetadata, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
import {LQueries, LQuery, QueryFlags, TQueries, TQuery, TQueryMetadata, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
import {DECLARATION_LCONTAINER, LView, PARENT, QUERIES, TVIEW, TView} from './interfaces/view';
import {assertTNodeType} from './node_assert';
import {getCurrentQueryIndex, getCurrentTNode, getLView, getTView, setCurrentQueryIndex} from './state';
@ -88,8 +88,8 @@ class LQueries_ implements LQueries {
class TQueryMetadata_ implements TQueryMetadata {
constructor(
public predicate: Type<any>|InjectionToken<unknown>|string[], public descendants: boolean,
public isStatic: boolean, public read: any = null) {}
public predicate: Type<any>|InjectionToken<unknown>|string[], public flags: QueryFlags,
public read: any = null) {}
}
class TQueries_ implements TQueries {
@ -202,7 +202,8 @@ class TQuery_ implements TQuery {
}
private isApplyingToNode(tNode: TNode): boolean {
if (this._appliesToNextNode && this.metadata.descendants === false) {
const isDescend = (this.metadata.flags & QueryFlags.descendants) === QueryFlags.descendants;
if (this._appliesToNextNode && !isDescend) {
const declarationNodeIdx = this._declarationNodeIndex;
let parent = tNode.parent;
// Determine if a given TNode is a "direct" child of a node on which a content query was
@ -427,14 +428,15 @@ export function ɵɵqueryRefresh(queryList: QueryList<any>): boolean {
setCurrentQueryIndex(queryIndex + 1);
const tQuery = getTQuery(tView, queryIndex);
if (queryList.dirty && (isCreationMode(lView) === tQuery.metadata.isStatic)) {
const isStatic = (tQuery.metadata.flags & QueryFlags.isStatic) === QueryFlags.isStatic;
if (queryList.dirty && (isCreationMode(lView) === isStatic)) {
if (tQuery.matches === null) {
queryList.reset([]);
} else {
const result = tQuery.crossesNgTemplate ?
collectQueryResults(tView, lView, queryIndex, []) :
materializeViewResults(tView, lView, tQuery, queryIndex);
queryList.reset(result);
queryList.reset(result, unwrapElementRef);
queryList.notifyOnChanges();
}
return true;
@ -447,40 +449,42 @@ export function ɵɵqueryRefresh(queryList: QueryList<any>): boolean {
* Creates new QueryList for a static view query.
*
* @param predicate The type for which the query will search
* @param descend Whether or not to descend into children
* @param flags Flags associated with the query
* @param read What to save in the query
*
* @codeGenApi
*/
export function ɵɵstaticViewQuery<T>(
predicate: Type<any>|InjectionToken<unknown>|string[], descend: boolean, read?: any): void {
viewQueryInternal(getTView(), getLView(), predicate, descend, read, true);
predicate: Type<any>|InjectionToken<unknown>|string[], flags: QueryFlags, read?: any): void {
ngDevMode && assertNumber(flags, 'Expecting flags');
viewQueryInternal(getTView(), getLView(), predicate, flags | QueryFlags.isStatic, read);
}
/**
* Creates new QueryList, stores the reference in LView and returns QueryList.
*
* @param predicate The type for which the query will search
* @param descend Whether or not to descend into children
* @param flags Flags associated with the query
* @param read What to save in the query
*
* @codeGenApi
*/
export function ɵɵviewQuery<T>(
predicate: Type<any>|InjectionToken<unknown>|string[], descend: boolean, read?: any): void {
viewQueryInternal(getTView(), getLView(), predicate, descend, read, false);
predicate: Type<any>|InjectionToken<unknown>|string[], flags: QueryFlags, read?: any): void {
ngDevMode && assertNumber(flags, 'Expecting flags');
viewQueryInternal(getTView(), getLView(), predicate, flags, read);
}
function viewQueryInternal<T>(
tView: TView, lView: LView, predicate: Type<any>|InjectionToken<unknown>|string[],
descend: boolean, read: any, isStatic: boolean): void {
flags: QueryFlags, read: any): void {
if (tView.firstCreatePass) {
createTQuery(tView, new TQueryMetadata_(predicate, descend, isStatic, read), -1);
if (isStatic) {
createTQuery(tView, new TQueryMetadata_(predicate, flags, read), -1);
if (flags & QueryFlags.isStatic) {
tView.staticViewQueries = true;
}
}
createLQuery<T>(tView, lView);
createLQuery<T>(tView, lView, flags);
}
/**
@ -489,17 +493,18 @@ function viewQueryInternal<T>(
*
* @param directiveIndex Current directive index
* @param predicate The type for which the query will search
* @param descend Whether or not to descend into children
* @param flags Flags associated with the query
* @param read What to save in the query
* @returns QueryList<T>
*
* @codeGenApi
*/
export function ɵɵcontentQuery<T>(
directiveIndex: number, predicate: Type<any>|InjectionToken<unknown>|string[], descend: boolean,
read?: any): void {
directiveIndex: number, predicate: Type<any>|InjectionToken<unknown>|string[],
flags: QueryFlags, read?: any): void {
ngDevMode && assertNumber(flags, 'Expecting flags');
contentQueryInternal(
getTView(), getLView(), predicate, descend, read, false, getCurrentTNode()!, directiveIndex);
getTView(), getLView(), predicate, flags, read, false, getCurrentTNode()!, directiveIndex);
}
/**
@ -508,31 +513,32 @@ export function ɵɵcontentQuery<T>(
*
* @param directiveIndex Current directive index
* @param predicate The type for which the query will search
* @param descend Whether or not to descend into children
* @param flags Flags associated with the query
* @param read What to save in the query
* @returns QueryList<T>
*
* @codeGenApi
*/
export function ɵɵstaticContentQuery<T>(
directiveIndex: number, predicate: Type<any>|InjectionToken<unknown>|string[], descend: boolean,
read?: any): void {
directiveIndex: number, predicate: Type<any>|InjectionToken<unknown>|string[],
flags: QueryFlags, read?: any): void {
ngDevMode && assertNumber(flags, 'Expecting flags');
contentQueryInternal(
getTView(), getLView(), predicate, descend, read, true, getCurrentTNode()!, directiveIndex);
getTView(), getLView(), predicate, flags, read, true, getCurrentTNode()!, directiveIndex);
}
function contentQueryInternal<T>(
tView: TView, lView: LView, predicate: Type<any>|InjectionToken<unknown>|string[],
descend: boolean, read: any, isStatic: boolean, tNode: TNode, directiveIndex: number): void {
flags: QueryFlags, read: any, isStatic: boolean, tNode: TNode, directiveIndex: number): void {
if (tView.firstCreatePass) {
createTQuery(tView, new TQueryMetadata_(predicate, descend, isStatic, read), tNode.index);
createTQuery(tView, new TQueryMetadata_(predicate, flags, read), tNode.index);
saveContentQueryAndDirectiveIndex(tView, directiveIndex);
if (isStatic) {
tView.staticContentQueries = true;
}
}
createLQuery<T>(tView, lView);
createLQuery<T>(tView, lView, flags);
}
/**
@ -551,8 +557,9 @@ function loadQueryInternal<T>(lView: LView, queryIndex: number): QueryList<T> {
return lView[QUERIES]!.queries[queryIndex].queryList;
}
function createLQuery<T>(tView: TView, lView: LView) {
const queryList = new QueryList<T>();
function createLQuery<T>(tView: TView, lView: LView, flags: QueryFlags) {
const queryList = new QueryList<T>(
(flags & QueryFlags.emitDistinctChangesOnly) === QueryFlags.emitDistinctChangesOnly);
storeCleanupWithContext(tView, lView, queryList, queryList.destroy);
if (lView[QUERIES] === null) lView[QUERIES] = new LQueries_();

View File

@ -20,6 +20,31 @@ export function addAllToArray(items: any[], arr: any[]) {
}
}
/**
* Determines if the contents of two arrays is identical
*
* @param a first array
* @param b second array
* @param identityAccessor Optional functions for extracting stable object identity from a value in
* the array.
*/
export function arrayEquals<T>(a: T[], b: T[], identityAccessor?: (value: T) => unknown): boolean {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
let valueA = a[i];
let valueB = b[i];
if (identityAccessor) {
valueA = identityAccessor(valueA) as any;
valueB = identityAccessor(valueB) as any;
}
if (valueB !== valueA) {
return false;
}
}
return true;
}
/**
* Flattens an array.
*/

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ElementRef} from '../linker/element_ref';
import {ElementRef, unwrapElementRef} from '../linker/element_ref';
import {QueryList} from '../linker/query_list';
import {asElementData, asProviderData, asQueryList, NodeDef, NodeFlags, QueryBindingDef, QueryBindingType, QueryDef, QueryValueType, ViewData} from './types';
@ -50,8 +50,8 @@ export function queryDef(
};
}
export function createQuery(): QueryList<any> {
return new QueryList();
export function createQuery(emitDistinctChangesOnly: boolean): QueryList<any> {
return new QueryList(emitDistinctChangesOnly);
}
export function dirtyParentQueries(view: ViewData) {
@ -107,7 +107,7 @@ export function checkAndUpdateQuery(view: ViewData, nodeDef: NodeDef) {
newValues = calcQueryValues(view, 0, view.def.nodes.length - 1, nodeDef.query!, []);
directiveInstance = view.component;
}
queryList.reset(newValues);
queryList.reset(newValues, unwrapElementRef);
const bindings = nodeDef.query!.bindings;
let notify = false;
for (let i = 0; i < bindings.length; i++) {

View File

@ -209,6 +209,7 @@ export const enum NodeFlags {
StaticQuery = 1 << 28,
DynamicQuery = 1 << 29,
TypeNgModule = 1 << 30,
EmitDistinctChangesOnly = 1 << 31,
CatQuery = TypeContentQuery | TypeViewQuery,
// mutually exclusive values...

View File

@ -327,7 +327,9 @@ function createViewNodes(view: ViewData) {
break;
case NodeFlags.TypeContentQuery:
case NodeFlags.TypeViewQuery:
nodeData = createQuery() as any;
nodeData = createQuery(
(nodeDef.flags & NodeFlags.EmitDistinctChangesOnly) ===
NodeFlags.EmitDistinctChangesOnly) as any;
break;
case NodeFlags.TypeNgContent:
appendNgContent(view, renderHost, nodeDef);

View File

@ -1123,6 +1123,46 @@ describe('query logic', () => {
fixture.detectChanges();
expect(changes).toBe(1);
});
it('should only fire if the content of the query changes', () => {
// When views are inserted/removed the content query need to be recomputed.
// Recomputing the query may result in no changes to the query (the item added/removed was
// not part of the query). This tests asserts that the query does not fire when no changes
// occur.
TestBed.configureTestingModule(
{declarations: [QueryCompWithStrictChangeEmitParent, QueryCompWithNoChanges]});
const fixture = TestBed.createComponent(QueryCompWithNoChanges);
let changesStrict = 0;
const componentInstance = fixture.componentInstance.queryComp;
fixture.detectChanges();
componentInstance.foos.changes.subscribe((value: any) => {
// subscribe to the changes and record when changes occur.
changesStrict += 1;
});
// First verify that the subscription is working.
fixture.componentInstance.innerShowing = false;
fixture.detectChanges();
expect(changesStrict).toBe(1); // We detected a change
expect(componentInstance.foos.toArray().length).toEqual(1);
// now verify that removing a view does not needlessly fire subscription
fixture.componentInstance.showing = false;
fixture.detectChanges();
expect(changesStrict).toBe(1); // We detected a change
expect(componentInstance.foos.toArray().length).toEqual(1);
// now verify that adding a view does not needlessly fire subscription
fixture.componentInstance.showing = true;
fixture.detectChanges();
expect(changesStrict).toBe(1); // We detected a change
// Note: even though the `showing` is `true` and the second `<div>` is displayed, the
// child element of that <div> is hidden because the `innerShowing` flag is still `false`,
// so we expect only one element to be present in the `foos` array.
expect(componentInstance.foos.toArray().length).toEqual(1);
});
});
describe('view boundaries', () => {
@ -1219,7 +1259,7 @@ describe('query logic', () => {
* - detect the situation where the indexes are the same and do no processing in such case.
*
* This tests asserts on the implementation choices done by the VE (detach and insert) so we
* can replicate the same behaviour in ivy.
* can replicate the same behavior in ivy.
*/
it('should notify on changes when a given view is removed and re-inserted at the same index',
() => {
@ -1964,6 +2004,37 @@ export class QueryCompWithChanges {
showing = false;
}
@Component({
selector: 'query-with-no-changes',
template: `
<query-component>
<div *ngIf="true" #foo></div>
<div *ngIf="showing">
Showing me should not change the content of the query
<div *ngIf="innerShowing" #foo></div>
</div>
</query-component>
`
})
export class QueryCompWithNoChanges {
showing: boolean = true;
innerShowing: boolean = true;
queryComp!: QueryCompWithStrictChangeEmitParent;
}
@Component({selector: 'query-component', template: `<ng-content></ng-content>`})
export class QueryCompWithStrictChangeEmitParent {
@ContentChildren('foo', {
descendants: true,
emitDistinctChangesOnly: true,
})
foos!: QueryList<any>;
constructor(public queryCompWithNoChanges: QueryCompWithNoChanges) {
queryCompWithNoChanges.queryComp = this;
}
}
@Component({selector: 'query-target', template: '<ng-content></ng-content>'})
class SuperDirectiveQueryTarget {
}

View File

@ -1982,6 +1982,9 @@
{
"name": "u"
},
{
"name": "unwrapElementRef"
},
{
"name": "unwrapRNode"
},

View File

@ -121,22 +121,24 @@ describe('component declaration jit compilation', () => {
static: true,
first: true,
read: ElementRef,
emitDistinctChangesOnly: false,
}
],
}) as ComponentDef<TestClass>;
expectComponentDef(def, {
contentQueries: functionContaining([
// "byRef" should use `contentQuery` with `false` for descendants flag without a read token,
// and bind to the full query result.
// "byRef" should use `contentQuery` with `0` (`QueryFlags.none`) for descendants flag
// without a read token, and bind to the full query result.
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
/(?:contentQuery|anonymous)[^(]*\(dirIndex,_c0,false\)/,
/(?:contentQuery|anonymous)[^(]*\(dirIndex,_c0,4\)/,
'(ctx.byRef = _t)',
// "byToken" should use `staticContentQuery` with `true` for descendants flag and
// `ElementRef` as read token, and bind to the first result in the query result.
// "byToken" should use `staticContentQuery` with `3`
// (`QueryFlags.descendants|QueryFlags.isStatic`) for descendants flag and `ElementRef` as
// read token, and bind to the first result in the query result.
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
/(?:staticContentQuery|anonymous)[^(]*\(dirIndex,[^,]*String[^,]*,true,[^)]*ElementRef[^)]*\)/,
/(?:contentQuery|anonymous)[^(]*\(dirIndex,[^,]*String[^,]*,3,[^)]*ElementRef[^)]*\)/,
'(ctx.byToken = _t.first)',
]),
});
@ -158,22 +160,24 @@ describe('component declaration jit compilation', () => {
static: true,
first: true,
read: ElementRef,
emitDistinctChangesOnly: false,
}
],
}) as ComponentDef<TestClass>;
expectComponentDef(def, {
viewQuery: functionContaining([
// "byRef" should use `viewQuery` with `false` for descendants flag without a read token,
// and bind to the full query result.
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
/(?:viewQuery|anonymous)[^(]*\(_c0,false\)/,
// "byRef" should use `viewQuery` with `0` (`QueryFlags.none`) for query flag without a read
// token, and bind to the full query result. NOTE: the `anonymous` match is to support IE11,
// as functions don't have a name there.
/(?:viewQuery|anonymous)[^(]*\(_c0,4\)/,
'(ctx.byRef = _t)',
// "byToken" should use `staticViewQuery` with `true` for descendants flag and
// `ElementRef` as read token, and bind to the first result in the query result.
// "byToken" should use `viewQuery` with `3`
// (`QueryFlags.descendants|QueryFlags.isStatic`) for descendants flag and `ElementRef` as
// read token, and bind to the first result in the query result.
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
/(?:staticViewQuery|anonymous)[^(]*\([^,]*String[^,]*,true,[^)]*ElementRef[^)]*\)/,
/(?:viewQuery|anonymous)[^(]*\([^,]*String[^,]*,3,[^)]*ElementRef[^)]*\)/,
'(ctx.byToken = _t.first)',
]),
});

View File

@ -95,22 +95,24 @@ describe('directive declaration jit compilation', () => {
static: true,
first: true,
read: ElementRef,
emitDistinctChangesOnly: false,
}
],
}) as DirectiveDef<TestClass>;
expectDirectiveDef(def, {
contentQueries: functionContaining([
// "byRef" should use `contentQuery` with `false` for descendants flag without a read token,
// and bind to the full query result.
// "byRef" should use `contentQuery` with `0` (`QueryFlags.descendants|QueryFlags.isStatic`)
// for descendants flag without a read token, and bind to the full query result.
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
/(?:contentQuery|anonymous)[^(]*\(dirIndex,_c0,false\)/,
/(?:contentQuery|anonymous)[^(]*\(dirIndex,_c0,4\)/,
'(ctx.byRef = _t)',
// "byToken" should use `staticContentQuery` with `true` for descendants flag and
// `ElementRef` as read token, and bind to the first result in the query result.
// "byToken" should use `viewQuery` with `3` (`QueryFlags.static|QueryFlags.descendants`)
// for descendants flag and `ElementRef` as read token, and bind to the first result in the
// query result.
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
/(?:staticContentQuery|anonymous)[^(]*\(dirIndex,[^,]*String[^,]*,true,[^)]*ElementRef[^)]*\)/,
/(?:contentQuery|anonymous)[^(]*\([^,]*dirIndex,[^,]*String[^,]*,3,[^)]*ElementRef[^)]*\)/,
'(ctx.byToken = _t.first)',
]),
});
@ -131,6 +133,7 @@ describe('directive declaration jit compilation', () => {
static: true,
first: true,
read: ElementRef,
emitDistinctChangesOnly: false,
}
],
}) as DirectiveDef<TestClass>;
@ -140,13 +143,14 @@ describe('directive declaration jit compilation', () => {
// "byRef" should use `viewQuery` with `false` for descendants flag without a read token,
// and bind to the full query result.
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
/(?:viewQuery|anonymous)[^(]*\(_c0,false\)/,
/(?:viewQuery|anonymous)[^(]*\(_c0,4\)/,
'(ctx.byRef = _t)',
// "byToken" should use `staticViewQuery` with `true` for descendants flag and
// `ElementRef` as read token, and bind to the first result in the query result.
// "byToken" should use `viewQuery` with `3` (`QueryFlags.static|QueryFlags.descendants`)
// for descendants flag and `ElementRef` as read token, and bind to the first result in the
// query result.
// NOTE: the `anonymous` match is to support IE11, as functions don't have a name there.
/(?:staticViewQuery|anonymous)[^(]*\([^,]*String[^,]*,true,[^)]*ElementRef[^)]*\)/,
/(?:viewQuery|anonymous)[^(]*\([^,]*String[^,]*,3,[^)]*ElementRef[^)]*\)/,
'(ctx.byToken = _t.first)',
]),
});

View File

@ -51,13 +51,15 @@ describe('jit directive helper functions', () => {
isViewQuery: false,
read: undefined,
static: false,
emitDistinctChangesOnly: false,
})).toEqual({
propertyName: 'propName',
predicate: ['localRef'],
descendants: false,
first: false,
read: null,
static: false
static: false,
emitDistinctChangesOnly: false,
});
});
@ -69,13 +71,15 @@ describe('jit directive helper functions', () => {
isViewQuery: true,
read: undefined,
static: false,
emitDistinctChangesOnly: false,
})).toEqual({
propertyName: 'propName',
predicate: ['foo', 'bar', 'baz'],
descendants: true,
first: true,
read: null,
static: false
static: false,
emitDistinctChangesOnly: false,
});
});
@ -88,7 +92,8 @@ describe('jit directive helper functions', () => {
first: true,
isViewQuery: true,
read: Directive,
static: false
static: false,
emitDistinctChangesOnly: false,
});
expect(converted.predicate).toEqual(Directive);

View File

@ -7,6 +7,7 @@
*/
import {ElementRef, QueryList, TemplateRef, ViewContainerRef} from '@angular/core';
import {QueryFlags} from '@angular/core/src/render3/interfaces/query';
import {HEADER_OFFSET} from '@angular/core/src/render3/interfaces/view';
import {AttributeMarker, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵProvidersFeature} from '../../src/render3/index';
@ -80,8 +81,8 @@ describe('query', () => {
2, 0, [Child], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(Child, false);
ɵɵviewQuery(Child, true);
ɵɵviewQuery(Child, QueryFlags.none);
ɵɵviewQuery(Child, QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -119,7 +120,7 @@ describe('query', () => {
1, 0, [Child], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(Child, false, ElementRef);
ɵɵviewQuery(Child, QueryFlags.none, ElementRef);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -158,7 +159,7 @@ describe('query', () => {
1, 0, [Child, OtherChild], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(Child, false, OtherChild);
ɵɵviewQuery(Child, QueryFlags.none, OtherChild);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -193,7 +194,7 @@ describe('query', () => {
1, 0, [Child, OtherChild], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(Child, false, OtherChild);
ɵɵviewQuery(Child, QueryFlags.none, OtherChild);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -263,9 +264,9 @@ describe('query', () => {
viewQuery:
function(rf: RenderFlags, ctx: App) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(MyDirective, false);
ɵɵviewQuery(Service, false);
ɵɵviewQuery(Alias, false);
ɵɵviewQuery(MyDirective, QueryFlags.none);
ɵɵviewQuery(Service, QueryFlags.none);
ɵɵviewQuery(Alias, QueryFlags.none);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -315,7 +316,7 @@ describe('query', () => {
function(rf: RenderFlags, ctx: App) {
let tmp: any;
if (rf & RenderFlags.Create) {
ɵɵviewQuery(MyDirective, false, Alias);
ɵɵviewQuery(MyDirective, QueryFlags.none, Alias);
}
if (rf & RenderFlags.Update) {
ɵɵqueryRefresh(tmp = ɵɵloadQuery<QueryList<any>>()) &&
@ -353,7 +354,7 @@ describe('query', () => {
3, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], false);
ɵɵviewQuery(['foo'], QueryFlags.none);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -392,8 +393,8 @@ describe('query', () => {
4, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], false);
ɵɵviewQuery(['bar'], false);
ɵɵviewQuery(['foo'], QueryFlags.none);
ɵɵviewQuery(['bar'], QueryFlags.none);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -441,7 +442,7 @@ describe('query', () => {
5, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo', 'bar'], false);
ɵɵviewQuery(['foo', 'bar'], QueryFlags.none);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -479,7 +480,7 @@ describe('query', () => {
3, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], false);
ɵɵviewQuery(['foo'], QueryFlags.none);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -517,7 +518,7 @@ describe('query', () => {
2, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], false, ElementRef);
ɵɵviewQuery(['foo'], QueryFlags.none, ElementRef);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -554,7 +555,7 @@ describe('query', () => {
2, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], true);
ɵɵviewQuery(['foo'], QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -588,7 +589,7 @@ describe('query', () => {
2, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], false, ViewContainerRef);
ɵɵviewQuery(['foo'], QueryFlags.none, ViewContainerRef);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -621,7 +622,7 @@ describe('query', () => {
2, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], false, ViewContainerRef);
ɵɵviewQuery(['foo'], QueryFlags.none, ViewContainerRef);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -655,7 +656,7 @@ describe('query', () => {
2, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], false, ElementRef);
ɵɵviewQuery(['foo'], QueryFlags.none, ElementRef);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -690,7 +691,7 @@ describe('query', () => {
2, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], false);
ɵɵviewQuery(['foo'], QueryFlags.none);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -724,7 +725,7 @@ describe('query', () => {
2, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], false, TemplateRef);
ɵɵviewQuery(['foo'], QueryFlags.none, TemplateRef);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -763,7 +764,7 @@ describe('query', () => {
2, 0, [Child], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], true);
ɵɵviewQuery(['foo'], QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -810,7 +811,7 @@ describe('query', () => {
2, 0, [Child], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], true);
ɵɵviewQuery(['foo'], QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -850,7 +851,7 @@ describe('query', () => {
2, 0, [Child], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], true);
ɵɵviewQuery(['foo'], QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -891,7 +892,7 @@ describe('query', () => {
3, 0, [Child1, Child2], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo', 'bar'], true);
ɵɵviewQuery(['foo', 'bar'], QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -932,8 +933,8 @@ describe('query', () => {
3, 0, [Child], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], true);
ɵɵviewQuery(['bar'], true);
ɵɵviewQuery(['foo'], QueryFlags.descendants);
ɵɵviewQuery(['bar'], QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -977,7 +978,7 @@ describe('query', () => {
2, 0, [Child], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], false, ElementRef);
ɵɵviewQuery(['foo'], QueryFlags.none, ElementRef);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1017,7 +1018,7 @@ describe('query', () => {
3, 0, [Child], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo', 'bar'], false);
ɵɵviewQuery(['foo', 'bar'], QueryFlags.none);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1053,7 +1054,7 @@ describe('query', () => {
2, 0, [Child], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], false, Child);
ɵɵviewQuery(['foo'], QueryFlags.none, Child);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1088,7 +1089,7 @@ describe('query', () => {
1, 0, [Child, OtherChild], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(Child, false, OtherChild);
ɵɵviewQuery(Child, QueryFlags.none, OtherChild);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1123,7 +1124,7 @@ describe('query', () => {
1, 0, [Child, OtherChild], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(OtherChild, false, Child);
ɵɵviewQuery(OtherChild, QueryFlags.none, Child);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1155,7 +1156,7 @@ describe('query', () => {
1, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(TemplateRef as any, false, ElementRef);
ɵɵviewQuery(TemplateRef as any, QueryFlags.none, ElementRef);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1188,7 +1189,7 @@ describe('query', () => {
2, 0, [Child], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], false, Child);
ɵɵviewQuery(['foo'], QueryFlags.none, Child);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1223,7 +1224,7 @@ describe('query', () => {
1, 0, [Child], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(TemplateRef as any, false);
ɵɵviewQuery(TemplateRef as any, QueryFlags.none);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1270,8 +1271,8 @@ describe('query', () => {
6, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(TemplateRef as any, false);
ɵɵviewQuery(TemplateRef as any, false, ElementRef);
ɵɵviewQuery(TemplateRef as any, QueryFlags.none);
ɵɵviewQuery(TemplateRef as any, QueryFlags.none, ElementRef);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1334,7 +1335,7 @@ describe('query', () => {
3, 0, [SomeDir], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], true);
ɵɵviewQuery(['foo'], QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1376,7 +1377,7 @@ describe('query', () => {
contentQueries:
(rf: RenderFlags, ctx: any, dirIndex: number) => {
if (rf & RenderFlags.Create) {
ɵɵcontentQuery(dirIndex, ['foo'], true);
ɵɵcontentQuery(dirIndex, ['foo'], QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1463,7 +1464,7 @@ describe('query', () => {
5, 0, [WithContentDirective], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo', 'bar'], true);
ɵɵviewQuery(['foo', 'bar'], QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1505,7 +1506,7 @@ describe('query', () => {
5, 0, [WithContentDirective], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['bar'], true);
ɵɵviewQuery(['bar'], QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1533,7 +1534,7 @@ describe('query', () => {
// @ContentChildren('foo, bar, baz', {descendants: true})
// fooBars: QueryList<ElementRef>;
if (rf & RenderFlags.Create) {
ɵɵcontentQuery(dirIndex, ['foo', 'bar', 'baz'], true);
ɵɵcontentQuery(dirIndex, ['foo', 'bar', 'baz'], QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1602,7 +1603,7 @@ describe('query', () => {
// @ContentChildren('foo', {descendants: true})
// fooBars: QueryList<ElementRef>;
if (rf & RenderFlags.Create) {
ɵɵcontentQuery(dirIndex, ['foo'], false);
ɵɵcontentQuery(dirIndex, ['foo'], QueryFlags.none);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1662,7 +1663,7 @@ describe('query', () => {
// @ContentChildren('foo', {descendants: true})
// fooBars: QueryList<ElementRef>;
if (rf & RenderFlags.Create) {
ɵɵcontentQuery(dirIndex, ['foo'], false);
ɵɵcontentQuery(dirIndex, ['foo'], QueryFlags.none);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1726,7 +1727,7 @@ describe('query', () => {
// @ContentChildren('foo', {descendants: false})
// foos: QueryList<ElementRef>;
if (rf & RenderFlags.Create) {
ɵɵcontentQuery(dirIndex, ['foo'], false);
ɵɵcontentQuery(dirIndex, ['foo'], QueryFlags.none);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1748,7 +1749,7 @@ describe('query', () => {
// @ContentChildren('foo', {descendants: true})
// foos: QueryList<ElementRef>;
if (rf & RenderFlags.Create) {
ɵɵcontentQuery(dirIndex, ['foo'], true);
ɵɵcontentQuery(dirIndex, ['foo'], QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1824,7 +1825,7 @@ describe('query', () => {
// @ContentChildren(TextDirective, {descendants: true})
// texts: QueryList<TextDirective>;
if (rf & RenderFlags.Create) {
ɵɵcontentQuery(dirIndex, TextDirective, true);
ɵɵcontentQuery(dirIndex, TextDirective, QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;
@ -1910,7 +1911,7 @@ describe('query', () => {
function(rf: RenderFlags, ctx: ViewQueryComponent) {
let tmp: any;
if (rf & RenderFlags.Create) {
ɵɵviewQuery(TextDirective, true);
ɵɵviewQuery(TextDirective, QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
ɵɵqueryRefresh(tmp = ɵɵloadQuery<QueryList<TextDirective>>()) &&

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {QueryFlags} from '@angular/core/src/render3/interfaces/query';
import {HEADER_OFFSET} from '@angular/core/src/render3/interfaces/view';
import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, ElementRef, QueryList, TemplateRef, ViewContainerRef, ViewRef} from '../../src/core';
import {ViewEncapsulation} from '../../src/metadata';
@ -368,7 +369,7 @@ describe('ViewContainerRef', () => {
viewQuery:
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], true);
ɵɵviewQuery(['foo'], QueryFlags.descendants);
}
if (rf & RenderFlags.Update) {
let tmp: any;