diff --git a/goldens/public-api/core/core.d.ts b/goldens/public-api/core/core.d.ts index 115a93cfbd..41aab0aeca 100644 --- a/goldens/public-api/core/core.d.ts +++ b/goldens/public-api/core/core.d.ts @@ -174,10 +174,12 @@ export declare type ContentChildren = Query; export declare interface ContentChildrenDecorator { (selector: Type | InjectionToken | Function | string, opts?: { descendants?: boolean; + emitDistinctChangesOnly?: boolean; read?: any; }): any; new (selector: Type | InjectionToken | 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 implements Iterable { [Symbol.iterator]: () => Iterator; - readonly changes: Observable; + get changes(): Observable; 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 implements Iterable { map(fn: (item: T, index: number, array: T[]) => U): U[]; notifyOnChanges(): void; reduce(fn: (prevValue: U, curValue: T, curIndex: number, array: T[]) => U, init: U): U; - reset(resultsTree: Array): void; + reset(resultsTree: Array, 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 | InjectionToken | Function | string, opts?: { read?: any; + emitDistinctChangesOnly?: boolean; }): any; new (selector: Type | InjectionToken | Function | string, opts?: { read?: any; + emitDistinctChangesOnly?: boolean; }): ViewChildren; } diff --git a/goldens/size-tracking/aio-payloads.json b/goldens/size-tracking/aio-payloads.json index 1538d4b8f8..369ce99ace 100755 --- a/goldens/size-tracking/aio-payloads.json +++ b/goldens/size-tracking/aio-payloads.json @@ -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 } } diff --git a/goldens/size-tracking/integration-payloads.json b/goldens/size-tracking/integration-payloads.json index 46a2dcb8e0..e1d22c89e1 100644 --- a/goldens/size-tracking/integration-payloads.json +++ b/goldens/size-tracking/integration-payloads.json @@ -39,7 +39,7 @@ "master": { "uncompressed": { "runtime-es2015": 2285, - "main-es2015": 240909, + "main-es2015": 241738, "polyfills-es2015": 36709, "5-es2015": 745 } diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_directive_linker_1.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_directive_linker_1.ts index 283c1b9474..e11c5e79c5 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_directive_linker_1.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_directive_linker_1.ts @@ -147,6 +147,8 @@ function toQueryMetadata(obj: AstObject `, 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: `
`, 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: `
`, 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: `
@@ -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: `
`, 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: `
`, 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: `
`, 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: `
@@ -652,3 +652,91 @@ export declare class MyModule { static ɵinj: i0.ɵɵInjectorDef; } +/**************************************************************************************************** + * 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; + static ɵdir: i0.ɵɵDirectiveDefWithMeta; +} + +/**************************************************************************************************** + * 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: ` +
+
+ `, isInline: true }); +(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ContentQueryComponent, [{ + type: Component, + args: [{ + selector: 'content-query-component', + template: ` +
+
+ ` + }] + }], 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; + oldMyRefs: QueryList; + someDirs: QueryList; + oldSomeDirs: QueryList; + static ɵfac: i0.ɵɵFactoryDef; + static ɵcmp: i0.ɵɵComponentDefWithMeta; +} +export declare class MyModule { + static ɵmod: i0.ɵɵNgModuleDefWithMeta; + static ɵinj: i0.ɵɵInjectorDef; +} + diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/TEST_CASES.json index dc986ab61e..a842eacb0a 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/TEST_CASES.json @@ -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" + ] + } + ] } ] } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/content_query_for_directive.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/content_query_for_directive.js index 536faeef07..faa1846e64 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/content_query_for_directive.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/content_query_for_directive.js @@ -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$; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/content_query_for_local_ref.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/content_query_for_local_ref.js index bf5c990ccb..06369baed7 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/content_query_for_local_ref.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/content_query_for_local_ref.js @@ -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$; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/content_query_read_token.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/content_query_read_token.js index 914ea33161..052641dff7 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/content_query_read_token.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/content_query_read_token.js @@ -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$; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/query_with_emit_distinct_changes_only.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/query_with_emit_distinct_changes_only.js new file mode 100644 index 0000000000..abf538c04c --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/query_with_emit_distinct_changes_only.js @@ -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$); + } + }, + //... +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/query_with_emit_distinct_changes_only.ts b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/query_with_emit_distinct_changes_only.ts new file mode 100644 index 0000000000..c00f5958fb --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/query_with_emit_distinct_changes_only.ts @@ -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: ` +
+
+ ` +}) +export class ContentQueryComponent { + @ContentChildren('myRef', {emitDistinctChangesOnly: true}) myRefs!: QueryList; + @ContentChildren('myRef', {emitDistinctChangesOnly: false}) oldMyRefs!: QueryList; + + @ViewChildren(SomeDirective, {emitDistinctChangesOnly: true}) someDirs!: QueryList; + @ViewChildren(SomeDirective, {emitDistinctChangesOnly: false}) oldSomeDirs!: QueryList; +} +@NgModule({declarations: [ContentQueryComponent]}) +export class MyModule { +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/static_content_query.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/static_content_query.js index 196aee6cda..e7053157b8 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/static_content_query.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/static_content_query.js @@ -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$; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/static_view_query.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/static_view_query.js index a64ab3d47a..1ece02dd91 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/static_view_query.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/static_view_query.js @@ -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$; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/view_query_for_directive.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/view_query_for_directive.js index c5bcbc05ef..beccd991aa 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/view_query_for_directive.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/view_query_for_directive.js @@ -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$; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/view_query_for_local_ref.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/view_query_for_local_ref.js index 2abc0e114b..f03ef5933e 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/view_query_for_local_ref.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/view_query_for_local_ref.js @@ -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$; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/view_query_read_token.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/view_query_read_token.js index d0f708ec7a..7daae2be02 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/view_query_read_token.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/queries/view_query_read_token.js @@ -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$; diff --git a/packages/compiler-cli/test/compliance_old/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance_old/r3_compiler_compliance_spec.ts index 06cae1931c..80989aaa55 100644 --- a/packages/compiler-cli/test/compliance_old/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance_old/r3_compiler_compliance_spec.ts @@ -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$; diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index ed827dcb3d..618467ae90 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -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', () => { diff --git a/packages/compiler/src/compile_metadata.ts b/packages/compiler/src/compile_metadata.ts index 80232d0aaf..e194e6183e 100644 --- a/packages/compiler/src/compile_metadata.ts +++ b/packages/compiler/src/compile_metadata.ts @@ -169,6 +169,7 @@ export interface CompileQueryMetadata { propertyName: string; read: CompileTokenMetadata; static?: boolean; + emitDistinctChangesOnly?: boolean; } /** diff --git a/packages/compiler/src/compiler_facade_interface.ts b/packages/compiler/src/compiler_facade_interface.ts index c0d1237134..336a5fa76c 100644 --- a/packages/compiler/src/compiler_facade_interface.ts +++ b/packages/compiler/src/compiler_facade_interface.ts @@ -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 { diff --git a/packages/compiler/src/core.ts b/packages/compiler/src/core.ts index d9360f45fa..8ac2250a30 100644 --- a/packages/compiler/src/core.ts +++ b/packages/compiler/src/core.ts @@ -27,6 +27,12 @@ export interface Attribute { export const createAttribute = makeMetadataFactory('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( - '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( 'ContentChild', (selector?: any, data: any = {}) => ({selector, first: true, isViewQuery: false, descendants: true, ...data})); export const createViewChildren = makeMetadataFactory( - '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( '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... diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index 605aa3e5f2..76d6236ad3 100644 --- a/packages/compiler/src/jit_compiler_facade.ts +++ b/packages/compiler/src/jit_compiler_facade.ts @@ -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, }; } diff --git a/packages/compiler/src/metadata_resolver.ts b/packages/compiler/src/metadata_resolver.ts index 17a247b4bd..c1d1e51314 100644 --- a/packages/compiler/src/metadata_resolver.ts +++ b/packages/compiler/src/metadata_resolver.ts @@ -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 diff --git a/packages/compiler/src/render3/partial/api.ts b/packages/compiler/src/render3/partial/api.ts index c64b2f49cb..38f8eee4e8 100644 --- a/packages/compiler/src/render3/partial/api.ts +++ b/packages/compiler/src/render3/partial/api.ts @@ -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. diff --git a/packages/compiler/src/render3/partial/directive.ts b/packages/compiler/src/render3/partial/directive.ts index c4d6cd0af3..8b5a854585 100644 --- a/packages/compiler/src/render3/partial/directive.ts +++ b/packages/compiler/src/render3/partial/directive.ts @@ -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)); } diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts index 4a207cc1a6..050efaba0a 100644 --- a/packages/compiler/src/render3/view/api.ts +++ b/packages/compiler/src/render3/view/api.ts @@ -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. diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index a6eb649a32..9c7ddce8c5 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -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[] = []; diff --git a/packages/compiler/src/view_compiler/view_compiler.ts b/packages/compiler/src/view_compiler/view_compiler.ts index 6f3254927c..b8c9f76c80 100644 --- a/packages/compiler/src/view_compiler/view_compiler.ts +++ b/packages/compiler/src/view_compiler/view_compiler.ts @@ -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; } diff --git a/packages/core/src/compiler/compiler_facade_interface.ts b/packages/core/src/compiler/compiler_facade_interface.ts index c0d1237134..336a5fa76c 100644 --- a/packages/core/src/compiler/compiler_facade_interface.ts +++ b/packages/core/src/compiler/compiler_facade_interface.ts @@ -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 { diff --git a/packages/core/src/linker/element_ref.ts b/packages/core/src/linker/element_ref.ts index dff2334d0e..8a9ff8133e 100644 --- a/packages/core/src/linker/element_ref.ts +++ b/packages/core/src/linker/element_ref.ts @@ -86,3 +86,14 @@ export class ElementRef { */ 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(value: T|ElementRef): T|R { + return value instanceof ElementRef ? value.nativeElement : value; +} \ No newline at end of file diff --git a/packages/core/src/linker/query_list.ts b/packages/core/src/linker/query_list.ts index 24f7f87b06..71f6e5fb4f 100644 --- a/packages/core/src/linker/query_list.ts +++ b/packages/core/src/linker/query_list.ts @@ -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(this: QueryList): Iterator { @@ -45,15 +45,31 @@ function symbolIterator(this: QueryList): Iterator { export class QueryList implements Iterable { public readonly dirty = true; private _results: Array = []; - public readonly changes: Observable = new EventEmitter(); + private _changesDetected: boolean = false; + private _changes: EventEmitter>|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 { + 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 implements Iterable { * 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): 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, identityAccessor?: (value: T) => unknown): void { + const self = this as QueryListInternal; + (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).emit(this); + if (this._changes && (this._emitDistinctChangesOnly ? this._changesDetected : true)) + this._changes.emit(this); } /** internal */ @@ -169,3 +192,14 @@ export class QueryList implements Iterable { // over QueryLists to work correctly, since QueryList must be assignable to NgIterable. [Symbol.iterator]!: () => Iterator; } + +/** + * Internal set of APIs used by the framework. (not to be made public) + */ +export interface QueryListInternal extends QueryList { + reset(a: any[]): void; + notifyOnChanges(): void; + length: number; + last: T; + first: T; +} \ No newline at end of file diff --git a/packages/core/src/metadata/di.ts b/packages/core/src/metadata/di.ts index 3b14ec3163..e33d78f3bf 100644 --- a/packages/core/src/metadata/di.ts +++ b/packages/core/src/metadata/di.ts @@ -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|InjectionToken|Function|string, - opts?: {descendants?: boolean, read?: any}): any; + (selector: Type|InjectionToken|Function|string, opts?: { + descendants?: boolean, + emitDistinctChangesOnly?: boolean, + read?: any, + }): any; new(selector: Type|InjectionToken|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|InjectionToken|Function|string, opts?: {read?: any}): any; + (selector: Type|InjectionToken|Function|string, + opts?: {read?: any, emitDistinctChangesOnly?: boolean}): any; new(selector: Type|InjectionToken|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); /** diff --git a/packages/core/src/render3/interfaces/query.ts b/packages/core/src/render3/interfaces/query.ts index f189a93d29..7281967dce 100644 --- a/packages/core/src/render3/interfaces/query.ts +++ b/packages/core/src/render3/interfaces/query.ts @@ -18,9 +18,39 @@ import {TView} from './view'; */ export interface TQueryMetadata { predicate: Type|InjectionToken|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, } /** diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts index a87d25f8e9..1b7085ebd3 100644 --- a/packages/core/src/render3/jit/directive.ts +++ b/packages/core/src/render3/jit/directive.ts @@ -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( diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index 89fa943ec3..eaa599d255 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -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|InjectionToken|string[], public descendants: boolean, - public isStatic: boolean, public read: any = null) {} + public predicate: Type|InjectionToken|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): 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): 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( - predicate: Type|InjectionToken|string[], descend: boolean, read?: any): void { - viewQueryInternal(getTView(), getLView(), predicate, descend, read, true); + predicate: Type|InjectionToken|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( - predicate: Type|InjectionToken|string[], descend: boolean, read?: any): void { - viewQueryInternal(getTView(), getLView(), predicate, descend, read, false); + predicate: Type|InjectionToken|string[], flags: QueryFlags, read?: any): void { + ngDevMode && assertNumber(flags, 'Expecting flags'); + viewQueryInternal(getTView(), getLView(), predicate, flags, read); } function viewQueryInternal( tView: TView, lView: LView, predicate: Type|InjectionToken|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(tView, lView); + createLQuery(tView, lView, flags); } /** @@ -489,17 +493,18 @@ function viewQueryInternal( * * @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 * * @codeGenApi */ export function ɵɵcontentQuery( - directiveIndex: number, predicate: Type|InjectionToken|string[], descend: boolean, - read?: any): void { + directiveIndex: number, predicate: Type|InjectionToken|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( * * @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 * * @codeGenApi */ export function ɵɵstaticContentQuery( - directiveIndex: number, predicate: Type|InjectionToken|string[], descend: boolean, - read?: any): void { + directiveIndex: number, predicate: Type|InjectionToken|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( tView: TView, lView: LView, predicate: Type|InjectionToken|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(tView, lView); + createLQuery(tView, lView, flags); } /** @@ -551,8 +557,9 @@ function loadQueryInternal(lView: LView, queryIndex: number): QueryList { return lView[QUERIES]!.queries[queryIndex].queryList; } -function createLQuery(tView: TView, lView: LView) { - const queryList = new QueryList(); +function createLQuery(tView: TView, lView: LView, flags: QueryFlags) { + const queryList = new QueryList( + (flags & QueryFlags.emitDistinctChangesOnly) === QueryFlags.emitDistinctChangesOnly); storeCleanupWithContext(tView, lView, queryList, queryList.destroy); if (lView[QUERIES] === null) lView[QUERIES] = new LQueries_(); diff --git a/packages/core/src/util/array_utils.ts b/packages/core/src/util/array_utils.ts index f8d819d334..90e5ffe8cf 100644 --- a/packages/core/src/util/array_utils.ts +++ b/packages/core/src/util/array_utils.ts @@ -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(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. */ diff --git a/packages/core/src/view/query.ts b/packages/core/src/view/query.ts index 83f4cae4a1..e60248ab82 100644 --- a/packages/core/src/view/query.ts +++ b/packages/core/src/view/query.ts @@ -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 { - return new QueryList(); +export function createQuery(emitDistinctChangesOnly: boolean): QueryList { + 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++) { diff --git a/packages/core/src/view/types.ts b/packages/core/src/view/types.ts index 930be64de2..caef9f00d7 100644 --- a/packages/core/src/view/types.ts +++ b/packages/core/src/view/types.ts @@ -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... diff --git a/packages/core/src/view/view.ts b/packages/core/src/view/view.ts index 949fac6e1e..9a3c1a737a 100644 --- a/packages/core/src/view/view.ts +++ b/packages/core/src/view/view.ts @@ -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); diff --git a/packages/core/test/acceptance/query_spec.ts b/packages/core/test/acceptance/query_spec.ts index 341f4f45c9..82da5b20e3 100644 --- a/packages/core/test/acceptance/query_spec.ts +++ b/packages/core/test/acceptance/query_spec.ts @@ -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 `
` is displayed, the + // child element of that
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: ` + +
+
+ Showing me should not change the content of the query +
+
+
+ ` +}) +export class QueryCompWithNoChanges { + showing: boolean = true; + innerShowing: boolean = true; + queryComp!: QueryCompWithStrictChangeEmitParent; +} + +@Component({selector: 'query-component', template: ``}) +export class QueryCompWithStrictChangeEmitParent { + @ContentChildren('foo', { + descendants: true, + emitDistinctChangesOnly: true, + }) + foos!: QueryList; + + constructor(public queryCompWithNoChanges: QueryCompWithNoChanges) { + queryCompWithNoChanges.queryComp = this; + } +} + @Component({selector: 'query-target', template: ''}) class SuperDirectiveQueryTarget { } diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index 02cdefc89a..d7ca828b94 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -1982,6 +1982,9 @@ { "name": "u" }, + { + "name": "unwrapElementRef" + }, { "name": "unwrapRNode" }, diff --git a/packages/core/test/render3/jit/declare_component_spec.ts b/packages/core/test/render3/jit/declare_component_spec.ts index 3cc81254a5..e395b1f1e1 100644 --- a/packages/core/test/render3/jit/declare_component_spec.ts +++ b/packages/core/test/render3/jit/declare_component_spec.ts @@ -121,22 +121,24 @@ describe('component declaration jit compilation', () => { static: true, first: true, read: ElementRef, + emitDistinctChangesOnly: false, } ], }) as ComponentDef; 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; 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)', ]), }); diff --git a/packages/core/test/render3/jit/declare_directive_spec.ts b/packages/core/test/render3/jit/declare_directive_spec.ts index e83e956098..08ef808b83 100644 --- a/packages/core/test/render3/jit/declare_directive_spec.ts +++ b/packages/core/test/render3/jit/declare_directive_spec.ts @@ -95,22 +95,24 @@ describe('directive declaration jit compilation', () => { static: true, first: true, read: ElementRef, + emitDistinctChangesOnly: false, } ], }) as DirectiveDef; 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; @@ -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)', ]), }); diff --git a/packages/core/test/render3/jit/directive_spec.ts b/packages/core/test/render3/jit/directive_spec.ts index 1e3aaee15c..5b41d82ec0 100644 --- a/packages/core/test/render3/jit/directive_spec.ts +++ b/packages/core/test/render3/jit/directive_spec.ts @@ -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); diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index df8c46bf46..b0bd6ece63 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -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>()) && @@ -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; 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; 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; 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; 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; 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; 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>()) && diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index a837c93c27..877bec2de8 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -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;