AngularJS - refresh view after http request, $rootScope.apply returns $digest already in progress
Asked Answered
C

2

3

I am simply trying to load data when my app starts. However, the view loads faster than the http request(of course). I want to refresh my view once my data has been properly loaded because that data defines my view.

I've tried $rootScope.apply from inside the factory where I do my http request, and I also tried directly doing the http request in my controller again with $scope.apply, and neither one worked as they both gave me "$digest already in progress"

Any idea how can I set up my code to make my views refresh on data load? I will be having several different http requests and I would like to know how to set them up properly! I would really appreciate any input!

Here is some of the code I am working with.

app.factory('HttpRequestFactory', function($http, $q) {
  var HttpRequestFactory = {
    async: function(url, params) {
      var deferred = $q.defer();
      $http({
        url: url,
        method: post,
        params: params
      })
        .success(function(data, status, headers, config) {
          deferred.resolve(data);
        })
        .error(function(data, status, headers, config) {
          deferred.reject("An error occurred");
        });
      return deferred.promise;
    }
  };
  return HttpRequestFactory;
});

Factory

function initializeAll(){   
    HttpRequestFactory.async('../api', {action: 'getall'}).then(function(data) {
            //$rootScope.$apply(function () {
                allData = data;
            //});
        angular.forEach(allData, function(value, index){
            console.log('Voala!');
        });
    });
}

Controller calling the factory's function initializeAll()

app.controller("MainController", ["$scope", "$rootScope","MyFactory", 
    function($scope, $rootScope, MyFactory){
        MyFactory.initializeAll();
    
    }
]);
Calculation answered 6/8, 2013 at 20:36 Comment(5)
can you create a jsfiddle? I really doubt whether your code is working or not. At least I can see some syntax error. method: post, will break your code since the post needs to be a string. thanks.Utter
hey sza, post is actually a constant that is declared in file and that is how it is working. I will try to get a jsFiddle.Calculation
Have you tried not to call $apply? The success callback of $http service is wrapped in a call to $apply by Angular itself.Deed
Yeah I tried without it. The problem is that I have to click on something to register some action to refresh my view and then all the data appears again.Calculation
I would look into using a resolve on the route. As usual, John Lindquist has an excellent video on the topic: youtube.com/watch?v=P6KITGRQujQAbound
D
3

Oh my !

You got the f** matter with AngularJS !

In fact you have to do a "safeApply" like that for example :

$rootScope.safeApply = function(fn) {
    var phase = this.$root.$$phase;
    if(phase == '$apply' || phase == '$digest') {
        if(fn && (typeof(fn) === 'function')) {
            fn();
        }
    } else {
        this.$apply(fn);
    }
};

In AngularJS you can only have one $apply or $digest loop at the same time.

For details on these loops look at the docs : http://docs.angularjs.org/guide/concepts

It will explain what is the $apply loop and you'll understand a lot of things about the two-way-data-binding in AngularJS

Hope it helps.

Dynode answered 17/9, 2013 at 12:26 Comment(0)
T
3

Don't use $apply: use $watch.

Calling $apply is (almost) always the wrong thing to do. The only time you should ever be calling it is if you've triggered a change outside of an 'angular' method; here, since the trigger occurs in an angular $http request, you can't call $apply because it's already being done at that moment by the $http block. Instead, what you want to do is $watch.

Official Doc for $scope.$watch() here

This will let you watch an object and update whenever it changes. I assume that your view is based on allData and you want it to update immediately; if you're using an ng method, then the watch is automatically setup for you and no more work should be needed. If you're using allData yourself inside a controller, you can write the watch in that controller like this:

$scope.$watch(function thingYouWantToWatch(){
         return <accessor call to allData here>;
      }, 
      function whatToDoOnChange(newValue, oldValue){
          $scope.myCoolThing = newValue; //this is the newValue of allData, or whatever you're watching.
      }
);
Trigon answered 30/12, 2013 at 21:32 Comment(1)
The official doc seems to have moved. Is this the equivalent now? docs.angularjs.org/api/ng/type/$rootScope.Scope#$watchAdahadaha

© 2022 - 2024 — McMap. All rights reserved.