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)
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.
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.
See "Scope $watch Depths" at https://docs.angularjs.org/guide/scope. Within is a particularly helpful graphic:
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.
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
'sbar
property andbaz
'squx
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 watchfoo
andbaz
, but you would be notified of any changes infoo
andbaz
(as opposed to just being notified for the changes onfoo
itself,foo.bar
andbaz.qux
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With