0

TL;DR: updating a property on a watched object does not seem to fire the watch when the property is a function .

Live example

See this Plnkr exampl.

The config object's properties can be changed by clicking the buttons in the example. The objects are in sync (between directive and controller), and the $watch function in the directive (which simply ups a counter in this case) is called when a property is changed. Except when a function property is changed, which is what I do not understand.

Dead example

View

<main-dir config="configObject"></main-dir> 

Directive

myApp.directive('mainDir', function () {

    return {
        restrict: 'E',
        scope: {config: '='},
        link: function (scope, el) {

            //(initialisation code)
                           
            scope.$watch("config", function (config) {
                if (!config) return;
                // do stuff with config object
            }, true);

         }
     };
 });   

Controller

//init:
$scope.configObject = {
    propA: "value",
    propB: [1,2,3],
    propC: {key0: 3, key1: "value"},
    propD: function(x) {return x+1;}
}

//elsewhere:
$scope.configObject.propA = "other value"; //fires watch in directive
//or
$scope.configObject.propB = [3,2,1]; //fires watch in directive
//or
$scope.configObject.propB.push(5); //fires watch in directive
//or
$scope.configObject.propC = {key0: 1, key2: 34}; //fires watch in directive
//or
$scope.configObject.propC["key100"] = "newValue"; //fires watch in directive
//
// ... BUT ...
//
$scope.configObject.propD = function (x) {return x+2;} //does NOT fire watch in directive

So, in short: I can get the $watch to fire on changes to the object's properties. But that doesn't seem to work if the property is a function.

Workaround:
When changing a property that is a function, I add a line changing a non-function property to fire the watch, like so:

$scope.configObject.propD = function (x) {/*...new function def...*/};
$scope.configObject.xxx = Math.random(); //<-- causes watch to fire

Does anyone know why that is the case? Can I get the $watch to fire also on changes to function properties of the config object?

Thanks!

1 Answer 1

1

scope.$watch("config.propD") will watch reference to function instead of returning value scope.$watch("config.xxx") will watch return value from Math.random because you called function

Simplest way is to use $watchCollection documentation but it's not perfect one

  scope.$watchCollection('config',
    function(config) {
      scope.caught++;
    }
  );

Another way is to program collection of $watch'ers it's work slightly better:

  angular.forEach(scope.config, function(value, key) {
    scope.$watch(function() {
      return scope.config[key];
    }, function(config) {
      scope.caught2++;
    }, true);
  });
Sign up to request clarification or add additional context in comments.

9 Comments

But I want to pass a function as propC - not its return value. The function is then used in the directive's link. Of course, it's more useful then returning 8.
But you want to $watch return value of function? So the ex I provided should work even if you pass function to directive
Hmm I'm not sure if I follow. config is an object, with various properties that I want to use in the directive. The directive should notice a change in the properties' values, even if a property is a function definition. (Imagine changing config.propC = function(x){return x^2;} to config.propC = function(x){return x^3;}. I could put a dedicated watch on config.propC, but I use a config object because it groups all related information needed to handle a change in either one of its properties. I hope that makes sense.
I could, I suppose, turn the config object (config = {propA: "val", propB: [1,2,3], propC: function(x){return x^2;}) into a config function returning that object (config = function(){return {propA: "val", propB: [1,2,3], propC: function(x){return x^2;};}) and then $watch not config but config(), but it seems unnecessarily complex. Moreover, I could then no longer individually change the property values of the object, defeating its purpose.
Hey Artur, thank you for your update. I've added a Plunker example to further clarify my issue (and changed the original question to reflect it) further, please have a look if you want. The problem with your edited approach is that the second $watch must do the exact same thing as the first. I could add a function that is called by both, but this is very unelegant and I cannot imagine there is not a better solution...
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.