Angular doesn't rely on magic or timeouts to detect changes. Instead, it uses a process called dirty-checking during each digest loop to determine if something, including a function return value, has changed. To better understand this process, let's examine your example and see how Angular handles changes and how you can notify Angular of such changes.
Step 1: Establish a watch
Looking at the source code of the ngShowDirective
, we can see the following:
var ngShowDirective = ['$animate', function($animate) {
return function(scope, element, attr) {
scope.$watch(attr.ngShow, function ngShowWatchAction(value){ // <= establish a watch
$animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide');
});
};
}];
The scope.$watch()
function takes an expression from the ng-show
attribute of a DOM element and compiles it into a function that returns true when the watched value changes. Since a function is passed, Angular will execute it to retrieve the new value.
Step 2: Listen to DOM events and run the digest loop
Angular listens to various DOM events to trigger the digest loop and check for changes whenever an event occurs in the browser. For example, here's what Angular does when the browser fires a hashchange
event:
$browser.onUrlChange(function(newUrl) {
if ($location.absUrl() != newUrl) {
$rootScope.$evalAsync(function() {
var oldUrl = $location.absUrl();
$location.$$parse(newUrl);
if ($rootScope.$broadcast('$locationChangeStart', newUrl,
oldUrl).defaultPrevented) {
$location.$$parse(oldUrl);
$browser.url(oldUrl);
} else {
afterLocationChange(oldUrl);
}
});
if (!$rootScope.$$phase) $rootScope.$digest(); // <= run the digest loop if it's not already running
}
});
Step 3: Evaluate watches and trigger callbacks for changed values
Scope.prototype = {
...
$digest: function() {
...
if ((value = watch.get(current)) !== (last = watch.last) &&
!(watch.eq
? equals(value, last)
: (typeof value == 'number' && typeof last == 'number'
&& isNaN(value) && isNaN(last)))) {
...
watch.fn(value, ((last === initWatchVal) ? value : last), current); // <= execute the watch callback function for changed values
...
}
...
}
}
Thus, this is how Angular determines whether to display
<ANY ng-show="aFunction()"> ... </ANY>
based on changes.
While Angular usually handles these processes automatically, there are scenarios where Angular may not detect changes. For instance, if a value changes outside of Angular's scope where watches are generated automatically. In such cases, manually calling scope.$digest()
or scope.$apply()
is necessary. One common example is changing a scope property value within a directive's event listener:
angular.module('demo').directive('clickable', function() {
return {
link: function(scope, element) {
element.bind('click', function() {
scope.$apply(function() { // <= invoke scope.$apply() to notify Angular of changes in the scope
scope.foo++;
});
});
}
}
});
Furthermore, it's advisable to use $apply()
with a callback function as it allows Angular to handle any errors that may arise during scope changes.
I hope this explanation clarifies how Angular tracks changes within the scope.
Observing a function's return value outside of Angular
If you wish to monitor a function's return value outside of Angular's application code, you can follow these steps:
- Obtain the top-level element of the application
- Access Angular's
$rootScope
- Monitor changes using
$watch()
Example:
var valueToBeChanged = 0,
rootElement = document.querySelector('[ng-app]'),
rootScope = angular.element(rootElement).scope();
function myFunc() {
return valueToBeChanged;
}
function myCallback() {
console.log('Value has been changed!');
}
rootScope.$watch(myFunc, myCallback);
angular.element(document.querySelector('body')).on('click', function() {
rootScope.$apply(function() {
valueToBeChanged++;
});
});
Plunker: http://plnkr.co/edit/LRK08EsP0jrOu9n42VwB?p=preview