-
Notifications
You must be signed in to change notification settings - Fork 24.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(core): QueryList
does not fire changes if the underlying list did not change.
#40091
Conversation
77480e0
to
f02babb
Compare
2cd900f
to
266fa09
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this looks good, thanks for debugging this.
Could you instead consider using the native Array#flat as this would simplify the code and in fact remove bulk of it.
We'll then also need to add
import 'core-js/modules/es.array.flat';
import 'core-js/modules/es.array.flat-map';
to
just so that we don't break on IE11 (the flat-map addition is not required by this change, but we might as well add it for future use).
If we target this fix for target: minor
then we can roll it out now since CLI would add the polyfill to ie11 builds in v11.1 as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (from code perspective), thanks for the fix @mhevery 👍
I was also thinking of using Array.flat
function as @IgorMinar proposed, but that'd result in additional array allocation and extra pass to compare current/new arrays (which is avoided in the current implementation). Given that QueryList.reset
is called for each CD run (executed from refreshView
fn), it looks like that might sensitive from perf perspective...
@IgorMinar |
29aa5b4
to
278dbce
Compare
(this.changes as EventEmitter<any>).complete(); | ||
(this.changes as EventEmitter<any>).unsubscribe(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably do that for both _changesDeprecated
and _changesStrict
event emitters (in case both emitters were used in an app).
(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]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is outside of the context of this PR, but it feels like these fields should be just getters on the QueryList
class (so setting their values would not be required) and that should also help with typings above.
FYI, started global presubmit. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd personally mark this for minor and not patch, since it introduces new API.
@@ -135,28 +169,36 @@ export class QueryList<T> implements Iterable<T> { | |||
* occurs. | |||
* | |||
* @param resultsTree The query results to store | |||
* @returns `true` only if the new `resultTree` resulted in a different query set. | |||
* @internal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although this is marked @internal
, it still shows up in the public api. As this is targeting patch I don't think we can/should hide this method in a patch release.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an internal method which should never be called by anyone. I can take it out if you feel strongly about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I'm fine with marking it internal, it's just that doing so could break projects who call this method manually. So we may need to go through a deprecation period.
Global presubmit #2 (using Ivy). |
You can preview af6d6cd at https://pr40091-af6d6cd.ngbuilds.io/. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Reviewed-for: size-tracking, public-api
You can preview 29c4f20 at https://pr40091-29c4f20.ngbuilds.io/. |
You can preview 469090e at https://pr40091-469090e.ngbuilds.io/. |
packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_directive_linker_1.ts
Outdated
Show resolved
Hide resolved
You can preview 4d81a44 at https://pr40091-4d81a44.ngbuilds.io/. |
… did not change. 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>; } ```
Because the query now has `flags` which specify the mode, the static query instruction can now be remove. It is simply normal query with `static` flag.
4d81a44
to
29cd0de
Compare
You can preview 29cd0de at https://pr40091-29cd0de.ngbuilds.io/. |
… 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
@mhevery this PR was merged into RC (11.1.x) and master branches, but there was a conflict of merging it to the patch branch (11.0.x). Could you please create a patch-only version of this change if needed? Thank you. |
I believe RC should be sufficient; there's no real need for this to land in 11.0.x with 11.1 just around the corner (there might not even be another 11.0.x release?) and porting this is non-trivial as the linker code doesn't exist in 11.0.x. |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Previous implementation would fire changes
QueryList.changes.subscribe
even if underlying list did not change. Such situation can arise if a
LView
is inserted or removed, causingQueryList
to recompute, butthe recompute results in the same exact
QueryList
result. In sucha case we don't want to fire a change event (as there is no change.)
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
Issue Number: N/A
What is the new behavior?
Does this PR introduce a breaking change?
Other information