There is no magic or timeout, the way Angular is understanding that something (including function return value) is changed is by performing dirty-checking each time digest loop is running. To understand how it works, let's look at your example and see the steps that Angular do for you and how you can tell Angular that something is changed.
Step 1: create watch
If we will look at the source code of ngShowDirective
we will see the following
var ngShowDirective = ['$animate', function($animate) {
return function(scope, element, attr) {
scope.$watch(attr.ngShow, function ngShowWatchAction(value){ // <= create watch
$animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide');
});
};
}];
The first parameter of scope.$watch()
is an expression that is taken from ng-show
attribute of the DOM element and compiled to a function that returns true in case watched value is changed. Since what is passed is a function - Angular will run it to fetch new value.
Step 2: subscribe to DOM events and run digest loop
Angular subscribes to many of the DOM events in order to run digest loop and check if something is changed every time something is happening in the browser. For example let's take a look what Angular do when browser fires 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 digest loop if it is not already running
}
});
Step 3: run all watches and call callbacks in case watch is changed
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); // <= run watch callback function in case watch value has been changed
...
}
...
}
}
That's it, that is how Angular knows when to make <ANY ng-show="aFunction()"> ... </ANY>
visible or not.
In most common cases Angular does all this staff for you, however there are some cases when Angular does not know that something is changed. For example when value is changed outside the Angular's scope where watches are created automatically. In these cases you should take care to call scope.$digest()
or scope.$apply()
yourself. One of the most common cases is changing scope property value in event listener of directive:
angular.module('demo').directive('clickable', function() {
return {
link: function(scope, element) {
element.bind('click', function() {
scope.$apply(function() { // <= call scope.$apply() in order to tell Angular that something is changed in the scope
scope.foo++;
});
});
}
}
});
Besides the fact that $apply()
calls to $digest()
, it is recommended to use $apply()
with callback function. That way Angular will know if something was wrong during scope change and will take care of Error in case there were any.
Hope it helped to understand how Angular knows about the changes in the scope.
Observing function return value outside Angular
If you want to leverage Angular's functionality of observing function return value outside the Angular's application code, you can do the following:
- Get top level element of the application
- Get Angular's
$rootScope
- Watch 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 have 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