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

feat($rootScope): Add custom compare and copy functions to $watch #10096

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Toilal
Copy link
Contributor

@Toilal Toilal commented Nov 17, 2014

Closes #10069

@@ -375,6 +377,14 @@ function $RootScopeProvider() {

lastDirtyWatch = null;

if (isFunction(objectEquality)) {
Copy link
Contributor Author

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.

@mary-poppins
Copy link

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.
PS2: If you are a Googler, please sign the CLA as well to simplify the CLA verification process.

@Toilal
Copy link
Contributor Author

Toilal commented Nov 17, 2014

CLA Should be OK now.

@Toilal
Copy link
Contributor Author

Toilal commented Nov 17, 2014

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.

@lgalfaso lgalfaso self-assigned this Nov 17, 2014
@mary-poppins
Copy link

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
@mary-poppins
Copy link

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).

@mary-poppins
Copy link

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).

@lgalfaso lgalfaso added this to the Backlog milestone Nov 19, 2014
@Toilal
Copy link
Contributor Author

Toilal commented Jan 7, 2015

Is there anything to do to see this getting merged ?

@googlebot googlebot added cla: yes and removed cla: no labels Jan 7, 2015
@lgalfaso
Copy link
Contributor

lgalfaso commented Jan 7, 2015

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:

  • we are 100% sure that equals and copy have no side-effects
  • we can control that equals(instance, copy(instance)

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 this to the copy and equals functions, but those are small nits that are easy to fix

@lgalfaso
Copy link
Contributor

lgalfaso commented Jan 9, 2015

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);

@lgalfaso lgalfaso modified the milestones: Ice Box, Backlog Jan 30, 2015
@Narretz Narretz assigned petebacondarwin and unassigned lgalfaso Sep 30, 2015
@petebacondarwin petebacondarwin modified the milestones: 1.5.x - migration-facilitation, Ice Box Sep 30, 2015
@petebacondarwin
Copy link
Contributor

@Narretz and I discussed a use for this functionality today inside ngModel.

I would like us to make a decision about whether we should include this:

  • It seems to me that the normal use cases are untouched by this PR, which means that unless you decide to provide your own custom equals and copy the watching would be the same in terms of semantic and performance.
  • While we would have no control over whether the custom equals and copy methods had side effects and were fast, this is not really any different than concerns over the user provided watcher functions.
  • Although this can be done using a helper, as suggested by @lgalfaso, this would be less performant than if it was embedded in the core root scope, and is more fiddly to set up.
  • I think we should ensure that a custom equals must be accompanied by a custom copy function.

* @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) {
Copy link
Contributor

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.

Copy link
Contributor

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?

Copy link
Contributor

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.

@petebacondarwin
Copy link
Contributor

TODO:

  • Update watch delegates with this functionality too
  • Check that both equals and copy are provided
  • Add better docs
  • Add bench press tests

eq: !!objectEquality
eq: !!objectEquality,
equals: isFunction(objectEquality) ? objectEquality : equals,
copy: isFunction(copier) ? copier : copy
Copy link
Contributor

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

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.

Proposal : Use a custom compare function for objectEquality in $scope.$watch
8 participants