Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

fix+perf($parse): allow watching array/object literal values, disable deep watch for one-way bindings #15301

Closed
wants to merge 4 commits into from

Conversation

jbedard
Copy link
Collaborator

@jbedard jbedard commented Oct 20, 2016

I think this is a better alternative to #15274, but has a breaking change (which didn't break any tests!).

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
Bug fix, perf

What is the current behavior? (You can also link to an open issue here)
Watching literals doesn't always work, literals passed to components are deep watched to work around this.

What is the new behavior (if this is a feature change)?
Watching literals now does a watch of the inputs.

Does this PR introduce a breaking change?
YES - see below or second commit

Please check if the PR fulfills these requirements

BREAKING CHANGE (65ff861):
Previously when a literal value was passed into a directive/component via one-way binding it would be watched with a deep watcher.

For example, for <my-component input="[a]">, a new instance of the array would be passed into the directive/component (and trigger $onChanges) not only if a changed but also if any sub property of a changed such as a.b or a.b.c.d.e etc.

This also means a new but equal value for a would NOT trigger such a change.

Now literal values use a non-deep watch similar to other directive/component one-way bindings. Changes are only trigger when the value itself changes.

Avoiding deep watchers for array/object literals will improve watcher performance of all literals passed as one-way bindings, especially those containing references to large/complex objects.

- the content of array/object literals is now watched for changes
- if the content changes then a new array/object will be constructed
…r literals

BREAKING CHANGE:
Previously when a literal value was passed into a directive/component via
one-way binding it would be watched with a deep watcher.

For example, for `<my-component input="[a]">`, a new instance of the array
would be passed into the directive/component (and trigger $onChanges) not
only if `a` changed but also if any sub property of `a` changed such as
`a.b` or `a.b.c.d.e` etc.

This also means a new but equal value for `a` would NOT trigger such a
change.

Now literal values use a non-deep watch similar to other directive/component
one-way bindings. Changes are only trigger when the value itself changes.

Avoiding deep watchers for array/object literals will improve watcher
performance of all literals passed as one-way bindings, especially those
containing references to large/complex objects.
@gkalpak
Copy link
Member

gkalpak commented Oct 20, 2016

Seems like a good thing (it has to wait for 1.7.0 though).
May be worth documenting - may be not.

@@ -3522,7 +3522,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
recordChanges(scopeName, newValue, oldValue);
destination[scopeName] = newValue;
}, deepWatch);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep watching never happens, but we still have a variable named deepWatch. Isn't it a bit misleading?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK so let's change deepWatch to isLiteral.

@petebacondarwin petebacondarwin modified the milestones: 1.7.0, Backlog Jan 23, 2017
@petebacondarwin petebacondarwin self-requested a review January 23, 2017 20:26
Copy link
Contributor

@petebacondarwin petebacondarwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks good. I would like to see a test in compileSpec.js that demonstrates that we are no longer deep watching - just for the record.

@@ -3522,7 +3522,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
recordChanges(scopeName, newValue, oldValue);
destination[scopeName] = newValue;
}, deepWatch);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK so let's change deepWatch to isLiteral.

Copy link
Contributor

@petebacondarwin petebacondarwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I guess I shouldn't approve this since I have asked for two changes:

  • rename deepWatch local variable to isLiteral
  • add test to compileSpec.js

@jbedard jbedard force-pushed the watch-literals-nodeepequals branch from 1fecb6f to 0706760 Compare January 27, 2017 08:53
@jbedard
Copy link
Collaborator Author

jbedard commented Jan 27, 2017

Renamed the var and added some tests. I think those should both be squashed with the second commit.

Note that c175619 (first commit) can actually go into 1.6 since it only adds support for literal-watching to $parse (without any use of equals like #15274 did). Then 65ff861 has the breaking change - dropping equals in one-way bindings.

Copy link
Member

@gkalpak gkalpak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder how this change affects watching object literals with computed property names.

@@ -4259,6 +4259,78 @@ describe('$compile', function() {
});


it('should trigger `$onChanges` for literal expressions when expression input value changes (simple value)', function() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add tests verifying that $onChanges is not called when a property of the input value changes?

Copy link
Contributor

@petebacondarwin petebacondarwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great. Thanks @jbedard
I will squash and merge. The first commit should probably be a feat, right? And I agree that it can go into 1.6

petebacondarwin pushed a commit that referenced this pull request Jan 27, 2017
The inputs of array/object literals are now watched for changes.
If the an input changes then a new instance of the literal will be
provided when the parsed expression is executed.

Closes #15301
petebacondarwin pushed a commit that referenced this pull request Jan 27, 2017
Avoiding deep watchers for array/object literals will improve watcher
performance of all literals passed as one-way bindings, especially those
containing references to large/complex objects.

BREAKING CHANGE:
Previously when a literal value was passed into a directive/component via
one-way binding it would be watched with a deep watcher.

For example, for `<my-component input="[a]">`, a new instance of the array
would be passed into the directive/component (and trigger $onChanges) not
only if `a` changed but also if any sub property of `a` changed such as
`a.b` or `a.b.c.d.e` etc.

This also means a new but equal value for `a` would NOT trigger such a
change.

Now literal values use an input-based watch similar to other directive/component
one-way bindings. In this context inputs are the non-constant parts of the
literal. In the example above the input would be `a`. Changes are only
triggered when the inputs to the literal change.

Closes #15301
ellimist pushed a commit to ellimist/angular.js that referenced this pull request Mar 15, 2017
The inputs of array/object literals are now watched for changes.
If the an input changes then a new instance of the literal will be
provided when the parsed expression is executed.

Closes angular#15301
ellimist pushed a commit to ellimist/angular.js that referenced this pull request Mar 15, 2017
Avoiding deep watchers for array/object literals will improve watcher
performance of all literals passed as one-way bindings, especially those
containing references to large/complex objects.

BREAKING CHANGE:
Previously when a literal value was passed into a directive/component via
one-way binding it would be watched with a deep watcher.

For example, for `<my-component input="[a]">`, a new instance of the array
would be passed into the directive/component (and trigger $onChanges) not
only if `a` changed but also if any sub property of `a` changed such as
`a.b` or `a.b.c.d.e` etc.

This also means a new but equal value for `a` would NOT trigger such a
change.

Now literal values use an input-based watch similar to other directive/component
one-way bindings. In this context inputs are the non-constant parts of the
literal. In the example above the input would be `a`. Changes are only
triggered when the inputs to the literal change.

Closes angular#15301
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants