Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between these watch methods in angularJS?

I have a problem while using a watch, i want to watch an object of array , lets suppose if any of the array from the object changes then watch should be fired so i'm confused about what to use for this purpose.

Can anyone help me to find difference between these two and suggest what to use in this circumstance.

Scope objects:

$scope.$watch('foo', fn)
$scope.$watch(function() {return $scope.foo}, fn);

$scope.$watchCollection('foo', fn)
$scope.$watchCollection(function() {return $scope.foo}, fn);

Non-scope objects:

$scope.$watch(obj.prop, fn)
$scope.$watch(function() {return obj.prop}, fn)

$scope.$watchCollection(obj.prop, fn)
$scope.$watchCollection(function() {return obj.prop}, fn)
like image 872
ngLover Avatar asked Jul 02 '15 11:07

ngLover


People also ask

What is watch function Angularjs?

What is the angular JS watch function? The angular JS $watch function is used to watch the scope object. The $watch keep an eye on the variable and as the value of the variable changes the angular JS $what runs a function. This function takes two arguments one is the new value and another parameter is the old value.

Where is rootScope defined?

Root Scope All applications have a $rootScope which is the scope created on the HTML element that contains the ng-app directive. The rootScope is available in the entire application. If a variable has the same name in both the current scope and in the rootScope, the application uses the one in the current scope.


2 Answers

See "Scope $watch Depths" at https://docs.angularjs.org/guide/scope. Within is a particularly helpful graphic:

enter image description here

Targeting a $scope property that is an 'object with Array properties' (if I understand the question correctly), I've demonstrated a few of the different $watcher configurations in the snippet below so that you can see the various behaviours in action.

(function() {
  "use strict";

  angular.module('myApp', [])
    .controller("Controller1", ['$scope', Controller1]);

  function Controller1($scope) {
    var _objWithArrayProps = {
        'prop1': [],
        'prop2': [],
        'prop3': []
      },
      _counts = {
        'watchNoObjEquality': -1, //watchers to -1 since they fire once upon initialisation
        'watchPropNoObjEquality': -1,
        'watchObjEquality': -1,
        'watchFunctionNoObjEquality': -1,
        'watchFunctionObjEquality': -1,
        'watchCollection': -1,
        'watchCollectionFunction': -1,
        'clicks': 0
      };

    $scope.data = {
      'objWithArrayProps': _objWithArrayProps,
      'counts': _counts
    };

    $scope.$watch('data.objWithArrayProps', watchNoObjEquality);
    $scope.$watch('data.objWithArrayProps.prop1', watchPropNoObjEquality);
    $scope.$watch('data.objWithArrayProps', watchObjEquality, true);
    $scope.$watch(watchFunction, watchFunctionNoObjEquality);
    $scope.$watch(watchFunction, watchFunctionObjEquality, true);
    $scope.$watchCollection('data.objWithArrayProps', watchCollection);
    $scope.$watchCollection(watchFunction, watchCollectionFunction);

    $scope.addProperty = addProperty;
    $scope.addArrayElements = addArrayElements;
    $scope.modifyProperties = modifyProperties;
    $scope.modifyArrayElements = modifyArrayElements;

    function watchNoObjEquality(newVal, oldVal) {
      _counts.watchNoObjEquality++;
    }

    function watchPropNoObjEquality(newVal, oldVal) {
      _counts.watchPropNoObjEquality++;
    }

    function watchObjEquality(newVal, oldVal) {
      _counts.watchObjEquality++;
    }

    function watchFunction() {
      return _objWithArrayProps; // same as: return $scope.data.objWithArrayProps;
    }

    function watchFunctionNoObjEquality(newVal, oldVal) {
      _counts.watchFunctionNoObjEquality++;
    }

    function watchFunctionObjEquality(newVal, oldVal) {
      _counts.watchFunctionObjEquality++;
    }

    function watchCollection(newVal, oldVal) {
      _counts.watchCollection++;
    }

    function watchCollectionFunction(newVal, oldVal) {
      _counts.watchCollectionFunction++;
    }

    var _propNameNumberSuffix = 4; //for naming properties incrementally
    function addProperty() {
      _counts.clicks++;
      _objWithArrayProps['prop' + _propNameNumberSuffix] = [];
      _propNameNumberSuffix++;
    }

    function addArrayElements() {
      _counts.clicks++;
      for (var prop in _objWithArrayProps) {
        if (_objWithArrayProps.hasOwnProperty(prop)) {
          _objWithArrayProps[prop].push('x');
        }
      }
    }

    function modifyProperties() {
      _counts.clicks++;
      for (var prop in _objWithArrayProps) {
        if (_objWithArrayProps.hasOwnProperty(prop)) {
          _objWithArrayProps[prop] = [Math.random()]; //set to a fresh array with a random number in it
        }
      }
    }

    function modifyArrayElements() {
      _counts.clicks++;
      for (var prop in _objWithArrayProps) {
        if (_objWithArrayProps.hasOwnProperty(prop)) {
          //ensure we have at least one item in array(s)
          if (!_objWithArrayProps[prop].length) {
            _objWithArrayProps[prop].push('x');
          }

          for (var i = 0, iLen = _objWithArrayProps[prop].length; i < iLen; i++) {
            _objWithArrayProps[prop][i] = Math.random().toFixed(4);
          }
        }
      }
    }

  }

})();
body {
  font-family: "Arial", "sans-serif";
  font-size: 0.8em;
}
h1 {
  font-size: 1.4em;
}
pre {
  margin: 0;
}
pre code {
  display: block;
  background-color: #eee;
  padding: 5px;
  max-height: 200px;
  overflow: auto;
}
ul.tab li span {
  display: inline-block;
  margin: 5px 0;
}
ul.tab li {
  border-bottom: 1px dashed #ddd;
  display: block;
  padding: 0;
}
ul.tab {
  padding: 0;
}
#data,
#counts {
  width: 45%;
  display: inline-block;
  vertical-align: top;
  margin: 2%;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
<div ng-app="myApp" ng-controller="Controller1">
  <h1>Object with Array properties</h1>
  <div id='counts'>
    <strong>Watcher fired count:</strong>
    <ul class="tab">
      <li><span><strong>$watch</strong>('objWithArrayProps', ...) :</span> {{data.counts.watchNoObjEquality}}</li>
      <li><span><strong>$watch</strong>('objWithArrayProps.prop1', ...) :</span> {{data.counts.watchPropNoObjEquality}}</li>
      <li><span><strong>$watch</strong>('objWithArrayProps', ..., true) :</span> {{data.counts.watchObjEquality}}</li>
      <li><span><strong>$watch</strong>(function() {...}, ...) :</span> {{data.counts.watchFunctionNoObjEquality}}</li>
      <li><span><strong>$watch</strong>(function() {...}, ..., true) :</span> {{data.counts.watchFunctionObjEquality}}</li>
      <li><span><strong>$watchCollection</strong>('objWithArrayProps', ...) :</span> {{data.counts.watchCollection}}</li>
      <li><span><strong>$watchCollection</strong>(function() {...}, ...) :</span> {{data.counts.watchCollectionFunction}}</li>
    </ul>
    <strong>Button clicks:</strong> {{data.counts.clicks}}
  </div>
  <div id='data'>
    <pre><code>{{data.objWithArrayProps | json}}</code></pre>
  </div>
  <button ng-click="addProperty()">Add a property</button>
  <button ng-click="addArrayElements()">Add array elements</button>
  <button ng-click="modifyProperties()">Modify properties</button>
  <button ng-click="modifyArrayElements()">Modify array elements</button>
</div>

(Plnkr <-- for easier experimentation)

$watch(..., ..., true)

As you can see in the snippet, if you want to guarantee you catch every little change to a nested object/array structure, use $scope.$watch(..., ..., true), where the 3rd parameter causes the watcher to do a deep comparison of every property within your object/arrays. It's relatively heavy to do this, however, so be aware there may be some performance implications on large objects.

$watch(function(){}, ..., ...)

The format $scope.$watch(function() {...}, ...) actually gives you a lot more flexibility than I've demonstrated in the snippet, however, where I'm basically just returning the object. The return value of your function will be compared against its previous return value (each time a $digest runs), so if you want the listener function to trigger you need to be returning a different value at the correct times. Ideally you want this function to be pretty light and simple for performance reasons (it will get executed a lot!), but depending on the data you need to watch you might be able to implement a better performing solution than $scope.$watch(..., ..., true).

$watchCollection(..., ...)

As you can see in the snippet, this responds only to shallow changes of the collection (whether it's an object or array) - it will notice if a property is added, deleted, moved, and if it contains a basic value like a String, Number, Boolean, etc, it will notice if it changes. However if your collection contains further nested Objects/Arrays, it will not notice changes within them (they're too deep). Thus the need for either of the above two $watch implementations.

ng-event

If the changes to the model you are interested in are only caused by user input, also remember to consider if you can use ng-change or ng-blur (etc) events on form controls instead of watchers on the model, as these will most likely be far better performing.

like image 138
JcT Avatar answered Oct 04 '22 21:10

JcT


To quote my answer from here, in which the OP never accepted an answer..

$watchCollection will shallow watch the properties on a single object and notify you if one of them changes.

$watchGroup however watches a group of individual watch expressions.

They are not functionally equivalent. $watchGroup could be used when you want to watch a multitude of expressions that should all respond to the same callback - these could be individual expressions that target different objects. This could be used for watching 'foo', 'foo.bar', and 'baz.qux'. These are 3 different targets - foo, foo's bar property and baz's qux property, but they will all delegate to the same handler.

By contrast, $watchCollection will only shallow watch a single object. This makes it better for an object that might change it's properties a lot (for example - our very own $scope). In keeping with our rubbish name examples, to achieve the same as above you would only want to watch foo and baz, but you would be notified of any changes in foo and baz (as opposed to just being notified for the changes on foo itself, foo.bar and baz.qux.

like image 34
Dan Avatar answered Oct 04 '22 20:10

Dan