Skip to content

Commit

Permalink
fix(core): QueryList should not fire changes if the underlying list…
Browse files Browse the repository at this point in the history
… did not change. (#40091)

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

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

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

PR Close #40091
  • Loading branch information
mhevery authored and AndrewKushnir committed Jan 14, 2021
1 parent 63bf613 commit 76f3633
Show file tree
Hide file tree
Showing 47 changed files with 719 additions and 227 deletions.
11 changes: 8 additions & 3 deletions goldens/public-api/core/core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,12 @@ export declare type ContentChildren = Query;
export declare interface ContentChildrenDecorator {
(selector: Type<any> | InjectionToken<unknown> | Function | string, opts?: {
descendants?: boolean;
emitDistinctChangesOnly?: boolean;
read?: any;
}): any;
new (selector: Type<any> | InjectionToken<unknown> | Function | string, opts?: {
descendants?: boolean;
emitDistinctChangesOnly?: boolean;
read?: any;
}): Query;
}
Expand Down Expand Up @@ -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;
Expand All @@ -746,12 +749,12 @@ export declare abstract class Query {

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

Expand Down
4 changes: 2 additions & 2 deletions goldens/size-tracking/aio-payloads.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 3033,
"main-es2015": 447349,
"main-es2015": 448090,
"polyfills-es2015": 52493
}
}
Expand All @@ -21,7 +21,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 3153,
"main-es2015": 431137,
"main-es2015": 432078,
"polyfills-es2015": 52493
}
}
Expand Down
2 changes: 1 addition & 1 deletion goldens/size-tracking/integration-payloads.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"master": {
"uncompressed": {
"runtime-es2015": 2285,
"main-es2015": 240909,
"main-es2015": 241738,
"polyfills-es2015": 36709,
"5-es2015": 745
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ function toQueryMetadata<TExpression>(obj: AstObject<R3DeclareQueryMetadata, TEx
first: obj.has('first') ? obj.getBoolean('first') : false,
predicate,
descendants: obj.has('descendants') ? obj.getBoolean('descendants') : false,
emitDistinctChangesOnly:
obj.has('emitDistinctChangesOnly') ? obj.getBoolean('emitDistinctChangesOnly') : true,
read: obj.has('read') ? obj.getOpaque('read') : null,
static: obj.has('static') ? obj.getBoolean('static') : false,
};
Expand Down
14 changes: 14 additions & 0 deletions packages/compiler-cli/src/ngtsc/annotations/src/directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import {compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, Expression, ExternalExpr, getSafePropertyAccessString, Identifiers, makeBindingParser, ParsedHostBindings, ParseError, parseHostBindings, R3DependencyMetadata, R3DirectiveDef, R3DirectiveMetadata, R3FactoryTarget, R3QueryMetadata, R3ResolvedDependencyType, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
import {emitDistinctChangesOnlyDefaultValue} from '@angular/compiler/src/core';
import * as ts from 'typescript';

import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
Expand Down Expand Up @@ -436,6 +437,7 @@ export function extractQueryMetadata(
let read: Expression|null = null;
// The default value for descendants is true for every decorator except @ContentChildren.
let descendants: boolean = name !== 'ContentChildren';
let emitDistinctChangesOnly: boolean = emitDistinctChangesOnlyDefaultValue;
if (args.length === 2) {
const optionsExpr = unwrapExpression(args[1]);
if (!ts.isObjectLiteralExpression(optionsExpr)) {
Expand All @@ -458,6 +460,17 @@ export function extractQueryMetadata(
descendants = descendantsValue;
}

if (options.has('emitDistinctChangesOnly')) {
const emitDistinctChangesOnlyExpr = options.get('emitDistinctChangesOnly')!;
const emitDistinctChangesOnlyValue = evaluator.evaluate(emitDistinctChangesOnlyExpr);
if (typeof emitDistinctChangesOnlyValue !== 'boolean') {
throw createValueHasWrongTypeError(
emitDistinctChangesOnlyExpr, emitDistinctChangesOnlyValue,
`@${name} options.emitDistinctChangesOnlys must be a boolean`);
}
emitDistinctChangesOnly = emitDistinctChangesOnlyValue;
}

if (options.has('static')) {
const staticValue = evaluator.evaluate(options.get('static')!);
if (typeof staticValue !== 'boolean') {
Expand All @@ -480,6 +493,7 @@ export function extractQueryMetadata(
descendants,
read,
static: isStatic,
emitDistinctChangesOnly,
};
}

Expand Down

0 comments on commit 76f3633

Please sign in to comment.