18

As digest cycle do the dirty checking of the variable that is if there are 100 scope variables and if I change one variable then it will run watch of all the variables.

Suppose I have 100 scope model variables that are independent of each other. If I make changes in one variable then I don't want to check all other 99 variables. Is there any way to do this ? If yes, how ?

4
  • As such there is no such way, but obiviously you can have bindonce directive to reduce the watcher from the page like {{::myVar}} which will watch for value only once & there after it wouldn't update a value Commented Jul 14, 2016 at 18:46
  • 1
    I can't use one time binding. This will remove watch from the variable and I need two way binding as I am using ng-model variables for input boxes
    – Sunil Garg
    Commented Jul 15, 2016 at 3:18
  • Maybe try using $watchCollection
    – malix
    Commented Aug 26, 2016 at 17:18
  • stackoverflow.com/questions/21141580/… Commented Aug 26, 2016 at 18:00

3 Answers 3

34
+50

Surprisingly, this is usually not a problem, Browsers don’t have problems even with thousands of bindings, unless the expressions are complex. The common answer for how many watchers are ok to have is 2000.

Solutions :

It is fairly easy onwards from AngularJS 1.3, since one-time bindings are in core now.

  1. One time Binding of the variables.

We can use One time binding(::) directive to prevent the watcher to watch the unwanted variables. Here, variable will be watch only once & after that it will not update that variable.

  1. Stop the digest cycle manually.

HTML :

<ul ng-controller="myCtrl">
  <li ng-repeat="item in Lists">{{lots of bindings}}</li>
</ul>

Controller Code :

app.controller('myCtrl', function ($scope, $element) {
  $element.on('scroll', function () {
    $scope.Lists = getVisibleElements();
    $scope.$digest();
  });
});

During the $digest, you are only interested in changes to Lists object, not changes to individual items. Yet, Angular will still interrogate every single watcher for changes.

directive for stop and pause the digest:

app.directive('stopDigest', function () {
  return {
    link: function (scope) {
      var watchers;

      scope.$on('stop', function () {
        watchers = scope.$$watchers;
        scope.$$watchers = [];
      });

      scope.$on('resume', function () {
        if (watchers)
          scope.$$watchers = watchers;
      });
    }
  };
});

Now, Controller code should be changed :

<ul ng-controller="listCtrl">
  <li stop-digest ng-repeat="item in visibleList">{{lots of bindings}}</li>
</ul>

app.controller('myCtrl', function ($scope, $element) {
  $element.on('scroll', function () {
    $scope.visibleList = getVisibleElements();

    $scope.$broadcast('stop');
    $scope.$digest();
    $scope.$broadcast('resume');
  });
});

Reference Doc : https://coderwall.com/p/d_aisq/speeding-up-angularjs-s-digest-loop

Thanks.

1
  • I came across this post looking to resolve my issue. I am having issues where a number of digest are created. Could you let me know how can I apply your logic and limit the digest cycles. Here is my post: stackoverflow.com/questions/53816868/…
    – aman
    Commented Dec 17, 2018 at 16:45
6

This is a good question and highlights one of the biggest deficiencies with Angular 1.x. There is little control over how the digest cycle is managed. It is meant to be a black box and for larger applications, this can cause significant performance issues. There is no angular way of doing what you suggest, but There is something that would help you achieve the same goals (ie- better performance of the digest cycle when only one thing changes).

I recommend using the bind-notifier plugin. I have no relationship with the project, but I am using it for my own project and have had great success with it.

The idea behind is that you can specify certain bindings to only be $digested when a specific event has been raised.

There are multiple ways of using the plugin, but here is the one that I find must effective:

In a template file, specify a binding using the special bind-notifier syntax:

<div>{{:user-data-change:user.name}}</div>
<div>{{:job-data-change:job.name}}</div>

These two bindings will not be dirty-checked on most digest cycles unless they are notified.

In your controller, when user data changes, notify the bindings like this:

this.refreshUserData().then(() => {
  $scope.$broadcast('$$rebind::user-data-change');
});

(and similar for job-data-changed)

With this, the bindings for user.name will only be checked on the broadcast.

A few things to keep in mind:

  1. This essentially subverts one of the key benefits of angular (also it's core weakness for large applications). Two way binding usually means that you don't need to actively manage changes to your model, but with this, you do. So, I would only recommend using this for the parts of your application that have lots of bindings and cause slowdowns.
  2. $emit and $broadcast themselves can affect performance, so try to only call them on small parts of the $scope tree (scopes with few or no children).
  3. Take a good look at the documentation since there are several ways to use the plugin. Choose the usage pattern that works best for your application.
3
  • How to apply it to ng-repeat ?
    – MSS
    Commented Jul 22, 2020 at 8:34
  • It will look like this: <div ng-repeat="item in :event-name:items">...</div> Commented Jul 22, 2020 at 18:39
  • And, I have a comment above stating that I have no relation to the project. Since then, I've taken ownership of it. So, even though my answer was correct when I wrote it, I don't want to mislead anyone. Commented Jul 22, 2020 at 18:40
1

This is quite a specific use-case to do exclusive/conditional checks in the digest cycle and I don't think it is possible without forking/hacking the angular core.

I would consider refactoring how/what you are $watching. Perhaps using ngModelController's $viewChangeListeners would be more suitable than $watch?

Not the answer you're looking for? Browse other questions tagged or ask your own question.