-
Notifications
You must be signed in to change notification settings - Fork 27.4k
feat($rootScope): Add custom compare and copy functions to $watch #10096
base: master
Are you sure you want to change the base?
Conversation
src/ng/rootScope.js
Outdated
@@ -375,6 +377,14 @@ function $RootScopeProvider() { | |||
|
|||
lastDirtyWatch = null; | |||
|
|||
if (isFunction(objectEquality)) { |
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.
Maybe if objectEquality is not a function, we could set angular.equals as the comparator function to avoid checking if comparator is undefined for each $digest cycle ?
Same for copier and angular.copy.
I'm sorry, but I wasn't able to verify your Contributor License Agreement (CLA) signature. CLA signature is required for any code contributions to AngularJS. Please sign our CLA and ensure that the CLA signature email address and the email address in this PR's commits match. If you signed the CLA as a corporation, please let us know the company's name. Thanks a bunch! PS: If you signed the CLA in the past then most likely the email addresses don't match. Please sign the CLA again or update the email address in the commit of this PR. |
CLA Should be OK now. |
4a3a578
to
81b1cc4
Compare
It seems to work well for my use case (angular-gantt). I've created a custom watcher with equals and copier supporting momentJS properly through lodash. See https://github.com/angular-gantt/angular-gantt/blob/history/src/plugins/history.js#L24-L53. |
CLA signature verified! Thank you! Someone from the team will now triage your PR and it will be processed based on the determined priority (doc updates and fixes with tests are prioritized over other changes). |
2 similar comments
CLA signature verified! Thank you! Someone from the team will now triage your PR and it will be processed based on the determined priority (doc updates and fixes with tests are prioritized over other changes). |
CLA signature verified! Thank you! Someone from the team will now triage your PR and it will be processed based on the determined priority (doc updates and fixes with tests are prioritized over other changes). |
Is there anything to do to see this getting merged ? |
I cannot speak for the rest of the team, but there is something that make me unease about this PR. The digest cycle is a critical part of the code, and every single change has consequences. This is why there is a strict control to know when code from angular is executed and when code outside angular is executed, and be able to handle them with the right care. In this specific case, there are two points:
If this PR is merged, then these assumptions are no longer true. I am specially worried about the first of these points The PR also makes other small mistakes as it exposes the watch object as |
The more I think about this, I think that most of what this PR brings can be done within a third party module. It would be something like /* Warning, untested code, and can use some cleanup */
angular.module('foo', []).service('watchHelper', function($parse) {
return function(expression, customEquals, customCopy, listener) {
var previousPreviousValue;
var previousValue;
var firstCall = true;
var counter = 0;
expression = $parse(expression);
return {
expression: function(scope, locals) {
var newValue = expression(scope, locals);
if (firstCall && !customEquals(newValue, previousValue)) {
counter++;
previousPreviousValue = previousValue;
previousValue = customCopy(newValue);
}
return counter;
},
listener: function(newValue, oldValue) {
if (newValue === oldValue) {
listener(previousValue, previousValue)
} else {
listener(previousValue, previousPreviousValue)
}
}
};
};
});
// Then, in your code you do
var foo = watchHelper(expression, customEquals, customCopy, listener);
$scope.$watch(foo.expression, foo.listener); |
@Narretz and I discussed a use for this functionality today inside I would like us to make a decision about whether we should include this:
|
* @returns {function()} Returns a deregistration function for this listener. | ||
*/ | ||
$watch: function(watchExp, listener, objectEquality) { | ||
$watch: function(watchExp, listener, objectEquality, copier) { | ||
var get = $parse(watchExp); | ||
|
||
if (get.$$watchDelegate) { |
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.
The $watchDelegate
also takes the objectEquality
parameter, so this would need to be refactored too. Luckily this is an internal interface so we might be able to get away with 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.
Do we need an extra test for expressions that generate a watchDelegate?
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.
Oh I forgot about this bit. There is a quite a bit of work to do here as each of the delegates in the parser need to be updated. We definitely need to test this.
TODO:
|
eq: !!objectEquality | ||
eq: !!objectEquality, | ||
equals: isFunction(objectEquality) ? objectEquality : equals, | ||
copy: isFunction(copier) ? copier : copy |
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.
if we decide to move forward, then this looks like the wrong approach. We should not add new properties, and change get
and listener
so they are new functions that do the custom compare and copy.
#10096 (comment) can be used as a template, but in this specific case it can be simplified by using the $watch
function closure.
If implemented that way, then there is no need to change any of the delegates
Closes #10069