1

I have a current-time directive using $timeout, and it appears to be invoking functions in a separate controller. I'm confused why this is, I thought Controllers contained their own scope and were isolated from each other?,

Basically, myFunction is being invoked when the currentTime directive $timeout occurs. Is it a big deal? Will this cause troubles for me down the road? Can someone help me understand this behavior? Here is a plunker that demonstrates the behavior I'm talking about.

Here is my HTML:

<html ng-app="myapp">

  <head> <!-- using Angular version 1.2.0-rc3 --> </head>
  <body>

    <div ng-controller="TimeController">
      <span current-time></span>
    </div>

    <div ng-controller="MyController">
      <span class="grrr">{{myFunction()}}</span>
    </div>

  </body>
</html>

My Angular code:

var myapp = angular.module('myapp', []);

// just to show that I'm using my currentTime directive in separate controller.
// but it still happens if remove TimeController.
myapp.controller('TimeController', ['$scope', function($scope) {
  console.log('entering TimeController');
}]);

myapp.controller('MyController', ['$scope', function($scope) {

  $scope.myFunction = function() {
    console.log('calling myFunction');
    return 'myFunction was last called @ ' + new Date().toString();
  };

}]);

// continuous update for time
myapp.directive('currentTime', ['$timeout', function($timeout) {

  return function(scope,element,attrs) {
    function updateTime() {
      $timeout(function() {
        element.text(new Date().toString());
        updateTime();
      }, 1000);
    };
    console.log('start clock');
    updateTime();
  };
}]);

2 Answers 2

5

TL:DR;

Change your directive to

    $timeout(function() {
      element.text(new Date().toString());
      updateTime();
    }, 1000, false);

And it won't trigger your myFunction()

Long story

When you do this

<span class="grrr">{{myFunction()}}</span>

You're basically telling Angular that the contents of the span should always equal the result of calling myFunction().

Now, Angular does not know what kinds of variables your function relies on to return a result, so whenever something changes, it needs to call that function again to see if it returns something else (this is called dirty-checking). Keep this in mind, it will be important once we look at the directive.

In the directive you're running $timeout every second. That triggers a $digest loop, which is basically Angular's way of detecting if things have changed. In the process of doing that it's checking ALL the scopes, which means it will also be calling your myFunction() to see if anything the directive did affected that. That's why it keeps getting called.

Now, if you don't want this behaviour, you could either bind to a variable instead of a function, OR if you're curious like me head over to the sourcecode for $timeout. I suppose you could look at the documentation as well, but to be honest most of the Angular docs aren't that good so I usually go for the source first.

There's an optional flag (and this one is even documented) called invokeApply, if you set it to false then $timeout will not trigger dirty-checking.

Sign up to request clarification or add additional context in comments.

Comments

2

That is the way that the $digest cycle works. When a $digest is triggered, angular will loop through all the $watch expressions and determine if they have changed. This happens for all $watch expressions on all scopes - a process called dirty-checking.

The reason that myFunction() is being called is because you have an implicit $watch on it as a result of the binding expression:

{{myFunction()}}

This is normal and nothing to be concerned about.

The reason that the $digest cycle is being triggered periodically is because of your $timeout function. By default, it will invoke your timeout function within an $apply block which, in turn, triggers the $digest. You can disable this behavior by passing false as the third argument to $timeout.

I would like to suggest that you use $interval instead of $timeout. It is better suited for this.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.