fix(core): Switch `emitDistinctChangesOnlyDefaultValue` to true (#41121)

BREAKING CHANGE:

Switching default of `emitDistinctChangesOnlyDefaultValue`
which changes the default behavior and may cause some applications which
rely on the incorrect behavior to fail.

`emitDistinctChangesOnly` flag has also been deprecated and will be
removed in a future major release.

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

Unfortunately, fixing the behavior outright caused too many existing
applications to fail. For this reason, Angular considers this fix a
breaking fix and has introduced a flag in `@ContentChildren` and
`@ViewChildren`, that controls the behavior.

```
export class QueryCompWithStrictChangeEmitParent {
  @ContentChildren('foo', {
    // This option is the new default with this change.
    emitDistinctChangesOnly: true,
  })
  foos!: QueryList<any>;
}
```
For backward compatibility before v12
`emitDistinctChangesOnlyDefaultValue` was set to `false. This change
changes the default to `true`.

PR Close #41121
This commit is contained in:
Misko Hevery 2021-03-08 11:11:21 -08:00 committed by Jessica Janiuk
parent 012a2b55e1
commit 70962465b5
20 changed files with 88 additions and 90 deletions

View File

@ -95,6 +95,7 @@ Tip: In the [API reference section](api) of this doc site, deprecated APIs are i
| [`ANALYZE_FOR_ENTRY_COMPONENTS`](api/core/ANALYZE_FOR_ENTRY_COMPONENTS) | none | v9 | See [`ANALYZE_FOR_ENTRY_COMPONENTS`](#entryComponents) |
| [`WrappedValue`](api/core/WrappedValue) | none | v10 | See [removing `WrappedValue`](#wrapped-value) |
| [`async`](api/core/testing/async) | [`waitForAsync`](api/core/testing/waitForAsync) | v11 | The `async` function from `@angular/core/testing` has been renamed to `waitForAsync` in order to avoid confusion with the native JavaScript `async` syntax. The existing function is deprecated and will be removed in a future version. |
[ `ViewChildren.emitDistinctChangesOnly` / `ContentChildren.emitDistinctChangesOnly` | none (was part of [issue #40091](https://github.com/angular/angular/issues/40091)) ] | This is a temporary flag introduced as part of bugfix of [issue #40091](https://github.com/angular/angular/issues/40091) and will be removed. |
{@a testing}
### @angular/core/testing

View File

@ -570,7 +570,7 @@ A.ɵdir = /*@__PURE__*/ ɵngcc0.ɵɵdefineDirective({ type: A, selectors: [["",
UndecoratedBase.ɵfac = function UndecoratedBase_Factory(t) { return new (t || UndecoratedBase)(); };
// TRANSPILED
UndecoratedBase.ɵdir = /*@__PURE__*/ ɵngcc0.ɵɵdefineDirective({ type: UndecoratedBase, viewQuery: function UndecoratedBase_Query(rf, ctx) { if (rf & 1) {
ɵngcc0.ɵɵviewQuery(_c0, 3);
ɵngcc0.ɵɵviewQuery(_c0, 7);
} if (rf & 2) {
let _t;
ɵngcc0.ɵɵqueryRefresh(_t = ɵngcc0.ɵɵloadQuery()) && (ctx.test = _t.first);

View File

@ -605,7 +605,7 @@ export function extractQueryMetadata(
if (typeof emitDistinctChangesOnlyValue !== 'boolean') {
throw createValueHasWrongTypeError(
emitDistinctChangesOnlyExpr, emitDistinctChangesOnlyValue,
`@${name} options.emitDistinctChangesOnlys must be a boolean`);
`@${name} options.emitDistinctChangesOnly must be a boolean`);
}
emitDistinctChangesOnly = emitDistinctChangesOnlyValue;
}

View File

@ -32,7 +32,7 @@ import * as i0 from "@angular/core";
export class ViewQueryComponent {
}
ViewQueryComponent.ɵfac = function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); };
ViewQueryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ViewQueryComponent, selector: "view-query-component", viewQueries: [{ propertyName: "someDir", first: true, predicate: SomeDirective, emitDistinctChangesOnly: false, descendants: true }, { propertyName: "someDirs", predicate: SomeDirective, emitDistinctChangesOnly: false, 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, descendants: true }, { propertyName: "someDirs", predicate: SomeDirective, descendants: true }], ngImport: i0, template: `
<div someDir></div>
`, isInline: true, directives: [{ type: i0.forwardRef(function () { return SomeDirective; }), selector: "[someDir]" }] });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ViewQueryComponent, [{
@ -88,7 +88,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"], emitDistinctChangesOnly: false, descendants: true }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], emitDistinctChangesOnly: false, 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"], descendants: true }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], descendants: true }], ngImport: i0, template: `
<div #myRef></div>
<div #myRef1></div>
`, isInline: true });
@ -170,7 +170,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, emitDistinctChangesOnly: false, descendants: true, static: true }, { propertyName: "foo", first: true, predicate: ["foo"], emitDistinctChangesOnly: false, 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, descendants: true, static: true }, { propertyName: "foo", first: true, predicate: ["foo"], descendants: true }], ngImport: i0, template: `
<div someDir></div>
`, isInline: true, directives: [{ type: i0.forwardRef(function () { return SomeDirective; }), selector: "[someDir]" }] });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ViewQueryComponent, [{
@ -252,7 +252,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"], 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: `
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: `
<div someDir></div>
<div #myRef></div>
<div #myRef1></div>
@ -344,7 +344,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, emitDistinctChangesOnly: false, descendants: true }, { propertyName: "someDirList", predicate: SomeDirective, emitDistinctChangesOnly: false }], 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, descendants: true }, { propertyName: "someDirList", predicate: SomeDirective }], ngImport: i0, template: `
<div><ng-content></ng-content></div>
`, isInline: true });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ContentQueryComponent, [{
@ -423,7 +423,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"], emitDistinctChangesOnly: false, descendants: true }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"], emitDistinctChangesOnly: false }], 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"], descendants: true }, { propertyName: "myRefs", predicate: ["myRef1, myRef2, myRef3"] }], ngImport: i0, template: `
<div #myRef></div>
<div #myRef1></div>
`, isInline: true });
@ -505,7 +505,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, emitDistinctChangesOnly: false, descendants: true, static: true }, { propertyName: "foo", first: true, predicate: ["foo"], emitDistinctChangesOnly: false, 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, descendants: true, static: true }, { propertyName: "foo", first: true, predicate: ["foo"], descendants: true }], ngImport: i0, template: `
<div><ng-content></ng-content></div>
`, isInline: true });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ContentQueryComponent, [{
@ -610,7 +610,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"], 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: `
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: `
<div someDir></div>
<div #myRef></div>
<div #myRef1></div>

View File

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

View File

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

View File

@ -5,10 +5,10 @@ ContentQueryComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
// ...
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) {
$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);
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, 5, TemplateRef);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 5, ElementRef);
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 4, ElementRef);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 4, TemplateRef);
}
if (rf & 2) {
let $tmp$;

View File

@ -4,8 +4,8 @@ ContentQueryComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵɵcontentQuery(
dirIndex, SomeDirective, __QueryFlags.isStatic__|__QueryFlags.descendants__);
$r3$.ɵɵcontentQuery(dirIndex, $ref0$, 1);
dirIndex, SomeDirective, __QueryFlags.isStatic__|__QueryFlags.descendants__|__QueryFlags.emitDistinctChangesOnly__);
$r3$.ɵɵcontentQuery(dirIndex, $ref0$, 5);
}
if (rf & 2) {
let $tmp$;

View File

@ -5,8 +5,8 @@ ViewQueryComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
selectors: [["view-query-component"]],
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵviewQuery(SomeDirective, __QueryFlags.isStatic__|__QueryFlags.descendants__);
$r3$.ɵɵviewQuery($refs$, 1);
$r3$.ɵɵviewQuery(SomeDirective, __QueryFlags.isStatic__|__QueryFlags.descendants__|__QueryFlags.emitDistinctChangesOnly__);
$r3$.ɵɵviewQuery($refs$, 5);
}
if (rf & 2) {
let $tmp$;

View File

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

View File

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

View File

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

View File

@ -1636,8 +1636,8 @@ describe('compiler compliance', () => {
selectors: [["view-query-component"]],
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵviewQuery(SomeDirective, 1);
$r3$.ɵɵviewQuery(SomeDirective, 1);
$r3$.ɵɵviewQuery(SomeDirective, 5);
$r3$.ɵɵviewQuery(SomeDirective, 5);
}
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$, 1);
$r3$.ɵɵviewQuery($e1_attrs$, 1);
$r3$.ɵɵviewQuery($e0_attrs$, 5);
$r3$.ɵɵviewQuery($e1_attrs$, 5);
}
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$.ɵɵviewQuery(SomeDirective, 3);
$r3$.ɵɵviewQuery($refs$, 1);
$r3$.ɵɵviewQuery(SomeDirective, 7);
$r3$.ɵɵviewQuery($refs$, 5);
}
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$, 1, TemplateRef);
$r3$.ɵɵviewQuery(SomeDirective, 1, ElementRef);
$r3$.ɵɵviewQuery($e1_attrs$, 1, ElementRef);
$r3$.ɵɵviewQuery(SomeDirective, 1, TemplateRef);
$r3$.ɵɵviewQuery($e0_attrs$, 5, TemplateRef);
$r3$.ɵɵviewQuery(SomeDirective, 5, ElementRef);
$r3$.ɵɵviewQuery($e1_attrs$, 5, ElementRef);
$r3$.ɵɵviewQuery(SomeDirective, 5, 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, 1);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 0);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 5);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 4);
}
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$, 1);
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 0);
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, 5);
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 4);
}
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$.ɵɵcontentQuery(dirIndex, SomeDirective, 3);
$r3$.ɵɵcontentQuery(dirIndex, $ref0$, 1);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 7);
$r3$.ɵɵcontentQuery(dirIndex, $ref0$, 5);
}
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$, 1, TemplateRef);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 1, ElementRef);
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 0, ElementRef);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 0, TemplateRef);
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, 5, TemplateRef);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 5, ElementRef);
$r3$.ɵɵcontentQuery(dirIndex, $e1_attrs$, 4, ElementRef);
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, 4, TemplateRef);
}
if (rf & 2) {
let $tmp$;

View File

@ -3133,10 +3133,10 @@ function allTests(os: string) {
expect(jsContents).toMatch(varRegExp('test1'));
expect(jsContents).toMatch(varRegExp('test2'));
expect(jsContents).toMatch(varRegExp('accessor'));
// 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));
// match `i0.ɵɵcontentQuery(dirIndex, _c1, 5, TemplateRef)`
expect(jsContents).toMatch(contentQueryRegExp('\\w+', 5, 'TemplateRef'));
// match `i0.ɵɵviewQuery(_c2, 5, null)`
expect(jsContents).toMatch(viewQueryRegExp('\\w+', 5));
});
it('should generate queries for directives', () => {
@ -3165,14 +3165,14 @@ function allTests(os: string) {
expect(jsContents).toMatch(varRegExp('test1'));
expect(jsContents).toMatch(varRegExp('test2'));
expect(jsContents).toMatch(varRegExp('accessor'));
// match `i0.ɵɵcontentQuery(dirIndex, _c1, 1, TemplateRef)`
expect(jsContents).toMatch(contentQueryRegExp('\\w+', 1, 'TemplateRef'));
// match `i0.ɵɵcontentQuery(dirIndex, _c1, 5, TemplateRef)`
expect(jsContents).toMatch(contentQueryRegExp('\\w+', 5, 'TemplateRef'));
// match `i0.ɵɵviewQuery(_c2, 1)`
// match `i0.ɵɵviewQuery(_c2, 5)`
// 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+', 1));
expect(jsContents).toMatch(viewQueryRegExp('\\w+', 5));
});
it('should handle queries that use forwardRef', () => {
@ -3194,13 +3194,13 @@ function allTests(os: string) {
env.driveMain();
const jsContents = env.getContents('test.js');
// 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)`
// match `i0.ɵɵcontentQuery(dirIndex, TemplateRef, 5, null)`
expect(jsContents).toMatch(contentQueryRegExp('TemplateRef', 5));
// match `i0.ɵɵcontentQuery(dirIndex, ViewContainerRef, 5, null)`
expect(jsContents).toMatch(contentQueryRegExp('ViewContainerRef', 5));
// match `i0.ɵɵcontentQuery(dirIndex, _c0, 5, null)`
expect(jsContents).toContain('_c0 = ["parens"];');
expect(jsContents).toMatch(contentQueryRegExp('_c0', 1));
expect(jsContents).toMatch(contentQueryRegExp('_c0', 5));
});
it('should handle queries that use an InjectionToken', () => {
@ -3221,10 +3221,10 @@ function allTests(os: string) {
env.driveMain();
const jsContents = env.getContents('test.js');
// 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));
// match `i0.ɵɵviewQuery(TOKEN, 5, null)`
expect(jsContents).toMatch(viewQueryRegExp('TOKEN', 5));
// match `i0.ɵɵcontentQuery(dirIndex, TOKEN, 5, null)`
expect(jsContents).toMatch(contentQueryRegExp('TOKEN', 5));
});
it('should compile expressions that write keys', () => {

View File

@ -28,9 +28,8 @@ export const createAttribute =
makeMetadataFactory<Attribute>('Attribute', (attributeName: string) => ({attributeName}));
// Stores the default value of `emitDistinctChangesOnly` when the `emitDistinctChangesOnly` is not
// explicitly set. This value will be changed to `true` in v12.
// TODO(misko): switch the default in v12 to `true`. See: packages/core/src/metadata/di.ts
export const emitDistinctChangesOnlyDefaultValue = false;
// explicitly set.
export const emitDistinctChangesOnlyDefaultValue = true;
export interface Query {

View File

@ -237,9 +237,6 @@ export interface R3DeclareQueryMetadata {
/**
* 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;
/**

View File

@ -87,9 +87,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`.
// `emitDistinctChangesOnly` is special because we expect it to be `true`.
// Therefore we explicitly emit the field, and explicitly place it only when it's `false`.
meta.set('emitDistinctChangesOnly', o.literal(false));
} else {
// The linker will assume that an absent `emitDistinctChangesOnly` flag is by default `true`.
}
if (query.descendants) {
meta.set('descendants', o.literal(true));

View File

@ -62,8 +62,7 @@ export class QueryList<T> implements Iterable<T> {
/**
* @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.
* the same result)
*/
constructor(private _emitDistinctChangesOnly: boolean = false) {
// This function should be declared on the prototype, but doing so there will cause the class

View File

@ -107,9 +107,8 @@ export interface Query {
}
// 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;
// explicitly set.
export const emitDistinctChangesOnlyDefaultValue = true;
/**
@ -148,8 +147,10 @@ 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.
* if the QueryList result has changed. When `false` the `changes` observable might emit even
* if the QueryList has not changed.
* ** Note: *** This config option is **deprecated**, it will be permanently set to `true` and
* removed in future versions of Angular.
* * **read** - Used to read a different token from the queried elements.
*
* @usageNotes
@ -287,8 +288,10 @@ 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.
* if the QueryList result has changed. When `false` the `changes` observable might emit even
* if the QueryList has not changed.
* ** Note: *** This config option is **deprecated**, it will be permanently set to `true` and
* removed in future versions of Angular.
*
* @usageNotes
*

View File

@ -1257,11 +1257,8 @@ describe('query logic', () => {
* - systematically detach and insert a view - this would result in unnecessary processing
* when the previous and new indexes for the move operation are the same;
* - 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 behavior in ivy.
*/
it('should notify on changes when a given view is removed and re-inserted at the same index',
it('should NOT notify on changes when a given view is removed and re-inserted at the same index',
() => {
@Component({
selector: 'test-comp',
@ -1298,7 +1295,7 @@ describe('query logic', () => {
vc.move(viewRef, 0);
fixture.detectChanges();
expect(queryList.length).toBe(1);
expect(fixture.componentInstance.queryListNotificationCounter).toBe(2);
expect(fixture.componentInstance.queryListNotificationCounter).toBe(1);
});
it('should support a mix of content queries from the declaration and embedded view', () => {