Controller Function Is Executed Multiple Times

By Dave Ceddia

Controller function executed multiple times

If you have created a controller with some functions, and then called one of those functions from a view, you might be wondering “why does the function get called multiple times?”

Here’s an example of a controller and a view that might exhibit this behavior:

angular.controller('UserCtrl', function(User) {
  var vm = this;

  vm.users = User.list();

  vm.isUserEnabled = function(user) {
    console.log('isUserEnabled');
    return user.active;
  };

  vm.userClasses = function(user) {
    console.log('userClasses');
    return []
      .concat(user.active ? ['user-active'] : [])
      .concat(user.loggedIn ? ['user-logged-in'] : [])
      .concat(user.isMe ? ['user-is-me'] : [])
      .join(' ');
  };

  vm.disableUser = function(user) {
    user.active = false;
  };
});
<div ng-controller="UserCtrl as uc">
  <ul>
    <li ng-repeat="user in uc.users" ng-class="uc.userClasses(user)">
      {{user.name}}
      <button ng-if="uc.isUserEnabled(user)"
              ng-click="uc.disableUser(user)">Disable</button>
    </li>
  </ul>
</div>

Try out the Plunker.

You’ll probably notice that userClasses and isUserEnabled will get called a bunch of times (watch the console). You might expect those functions would only be called once per user, but it’s more like twice per user. And if you click that Disable button, the functions will run 3 times. What’s going on?

Angular’s Digest Cycle

What you are seeing is the digest cycle at work. The digest cycle is how Angular’s auto-update magic works – it’s the reason that typing into an input box automatically updates anything that refers to its value.

When the digest cycle runs, it effectively redraws everything that might have changed on the page.

Angular uses some tricks to find “everything that might have changed”, and the main technique is watchers. These watchers are created automatically when you use directives like ng-if and ng-class, and when you use bindings like {{ yourBindingHere }}.

Each one of those things registers a watcher. When Angular’s digest cycle runs, every watcher is asked to update its state. In the case of ng-class, it will re-run the function bound to it, to see if anything needs to change. This is why your controller function runs multiple times, and it’ll run again each time something changes on the page.

Nothing to worry about…mostly

Your bound functions being called multiple times is perfectly normal. There’s nothing wrong. But there are a couple things to be aware of:

  • Keep these functions fast – Since watchers will execute many times through your app’s lifetime, these functions should return quickly. Avoid doing slow operations like searching or sorting an array inside a watcher.

  • Don’t have too many watchers on one page – The rule of thumb is to keep it under 2000, otherwise your app will start to feel sluggish.

  • If your app gets slow, suspect watchers – It’s pretty easy to register a lot of watchers without realizing it. ng-stats can tell you how long your digest cycles are taking, and how many watchers you have. It can be used as a bookmarklet too, so you shouldn’t even need to install it.

Learning React can be a struggle — so many libraries and tools!
My advice? Ignore all of them :)
For a step-by-step approach, check out my Pure React workshop.

Pure React plant

Learn to think in React

  • 90+ screencast lessons
  • Full transcripts and closed captions
  • All the code from the lessons
  • Developer interviews
Start learning Pure React now

Dave Ceddia’s Pure React is a work of enormous clarity and depth. Hats off. I'm a React trainer in London and would thoroughly recommend this to all front end devs wanting to upskill or consolidate.

Alan Lavender
Alan Lavender
@lavenderlens