Make AngularJS skip running a digest loop if $http.get() resulted in no new data
Asked Answered
U

3

14

I'm currently polling the server to check for new data, and then update the model in an AngularJS app accordingly. He're roughly what I'm doing:

setInterval(function () {
    $http.get('data.json').then(function (result) {
        if (result.data.length > 0) {
          // if data, update model here
        } else {
          // nothing has changed, but AngularJS will still start the digest cycle
        }
    });
}, 5000);

This works fine, but most of the requests will not result in any new data or data changes, but the $http service doesn't really know/care and will still trigger a digest cycle. I feel this is unnecessary (since the digest cycle is one of the heaviest operations in the app). Is there any way to still be able to use $http but somehow skip the digest if nothing has changed?

One solution would be to not use $http but jQuery instead, and then call $apply to let Angular know that the model has changed:

setInterval(function () {
    $.get('data.json', function (dataList) {
        if (dataList.length > 0) {
            // if data, update model
            $scope.value = dataList[0].value + ' ' + new Date();

            // notify angular manually that the model has changed.
            $rootScope.$apply();
        }
    });
}, 5000);

While this seems to work, I'm not sure it's a good idea. I would still like to use pure Angular if possible.

Anyone got any suggestions for improvements to the approach above or a more elegant solution entirely?

P.S. The reason I'm using setInterval instead of $timeout is because $timeout would also trigger a digest cycle which would be unnecessary in this case and only add to the "problem".

Unspeakable answered 21/2, 2014 at 13:1 Comment(4)
Sorry to bother you but where have you read that $http triggers a digest cycle (if you don't put data in the scope) ?Comparison
$http will trigger an $apply and so this will trigger a digest cycle.Ottava
I discovered that $timeout takes an optional parameter called "invokeApply", which if set to false will skip the digest loop. It would make sense if $http would provide the same option as well.Unspeakable
Facing a similar issue. After making $hhtp call and updating the original object, the view (input box) bound to it gets updated, however the original object property somehow again is undefined at the end. The only additional thing I have is an ng-change attached to this input box. This ng-change also gets triggered automatically after http. Can someone suggest something??Decoder
V
1

Web sockets would seem to be the most elegant solution here. That way you don't need to poll the server. The server can tell your app when data or anything has changed.

Virescence answered 21/2, 2014 at 18:31 Comment(3)
I agree that web sockets is the most elegant solution, but it's not likely to happen at the moment in our project. Polling works well in this case and is very stable and predictable.Unspeakable
@Unspeakable so is letting the digest cycle happen taking too much time? Even if it seems like a waste, if it isnt slowing down your app is it a big deal?Virescence
It's not taking that much time, but it's an app that is meant to run all day on mobile devices, and I would like to not run a lot of JavaScript unnecessarily in order to save battery. But you're right, it's not a big deal.Unspeakable
H
7
  1. Solution provided by AngularJS @doc

AngularJS recommends to use a PERF trick that would bundle up a few $http responses in one $digest via $httpProvider. This again, is not fixing the problem, it's just a sedative :)

$httpProvider.useApplyAsync(true)

  1. Saving the $$watchers solution - risky and not scalable

Firstly, the accepted solution is not scalable - there's no way you're going to do that $watchers trick on a 100K lines of JS code project - it's out of the question.

Secondly, even if the project is small, it's quite risky! What happens for instance if another ajax call arrives that actually needs those watchers?


  1. Another (feasible) risky solution

The only alternative to achieve this without modifying AngularJS code would be to set the $rootScope.$$phase to true or '$digest', make the $http call, and set back the $rootScope.$$phase to null.

 $rootScope.$$phase = true;
 $http({...})
   .then(successcb, failurecb)
   .finally(function () {
       $rootScope.$$phase = null;            
   });

Risks:

1) other ajax calls might try to do the same thing --> they need to be synchronized via a wrapping ajax service (over $http)

2) user can trigger UI actions in between, actions that will change the $$phase to null and when the ajax call will come back, and still trigger the $digest

The solution popped after scanning AngularJS source code - here's the line that saves the situation: https://github.com/angular/angular.js/blob/e5e0884eaf2a37e4588d917e008e45f5b3ed4479/src/ng/http.js#L1272


  1. The ideal solution

Because this is a problem that everyone is facing with AngularJS, I think it needs to be addressed systematically. The answers above are not fixing the problem, are only trying to avoid it. So we should create a AngularJS pull request that would allow us to specify via $httpProvider a config that would not trigger a digest for a specific $http request. Hopefully they agree that this needs to be addressed somehow.

Hillell answered 24/11, 2015 at 19:2 Comment(0)
S
1

You can do it by this trick :

var watchers;

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

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

With this you will remove your scope or reinsert it on the $digest cycle.

You have to manage events to do that of course.

Refer to this post :

Remove and restore Scope from digest cycles

Hope it helps !

Sate answered 21/2, 2014 at 13:36 Comment(2)
Interesting. Are you saying I could "suspend" the root scope in case $http didn't result in any changes, and then somehow "resume" the rootScope again once $http has completed? Modifying $$watchers, which isn't part of Angular's public API, feels a bit wrong though. But I learnt something new!Unspeakable
You can but it's a 'hack' ! Break the digest cycle is to change the Angular Philosophy you'll never get an elegant solution with the AngularJS API ! It's like check the $$phase to know if a digest or apply loop is in progress !Sate
V
1

Web sockets would seem to be the most elegant solution here. That way you don't need to poll the server. The server can tell your app when data or anything has changed.

Virescence answered 21/2, 2014 at 18:31 Comment(3)
I agree that web sockets is the most elegant solution, but it's not likely to happen at the moment in our project. Polling works well in this case and is very stable and predictable.Unspeakable
@Unspeakable so is letting the digest cycle happen taking too much time? Even if it seems like a waste, if it isnt slowing down your app is it a big deal?Virescence
It's not taking that much time, but it's an app that is meant to run all day on mobile devices, and I would like to not run a lot of JavaScript unnecessarily in order to save battery. But you're right, it's not a big deal.Unspeakable

© 2022 - 2024 — McMap. All rights reserved.