Question

In a browser, for an object, a property has its getter and setter. We can override them to watch the attribute. How about the return value of a function? In AngularJS, use 'ng-show' to control the visibility of a component. It can write like this:

<ANY ng-show="aFunction()"> ... </ANY>

The function may be defined as:

var tmp = false;
$scope.aFunction = function() { return tmp; };

If in runtime, 'tmp' is changed to true, the < ANY > will show up. How does AngularJS know the return value is changed? Is there a timeout function to check changes regularly?

Was it helpful?

Solution

There is no timeout function. There is an AngularJS-specific concept, called "digest-cycle".
You can learn more about the scope life-cycle from the docs.
I have also found this article to be quite useful for an introduction to this mechanism.
Finally, I am sure there are plenty of related answers here on SO.

In general, you should try to find out exactly what the functions $apply(), $digest(), $watch() do.

OTHER TIPS

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:

  1. Get top level element of the application
  2. Get Angular's $rootScope
  3. 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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top