6

I have an ajax call inside the .run() that loads a variable into the $rootScope That variable is needed in the controller associated with a view. Sometimes on refresh (F5) by the time the .controller is loading there is nothing inside $rootScope.SuperCategories.

sampleApp.factory('SuperCategoryService', ['$http', '$q', '$rootScope', function ($http, $q, $rootScope){

    var SuperCategoryService =  {};
    SuperCategoryService.SuperCategories = $rootScope.SuperCategories;
    alert ($rootScope.SuperCategories);
    return SuperCategoryService;

}]);



sampleApp.run(function($rootScope, $q, $http) {

    var req = {
        method: 'POST',
        url: 'SuperCategoryData.txt',
        //url: 'http://localhost/cgi-bin/superCategory.pl',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' }//,
        //data: { action: 'GET' }
    };


    $rootScope.SuperCategories = [];

    $rootScope.GetSuperCategories = function () {

        var defer = $q.defer();
        $http(screq).then(function(response) {
            $rootScope.SuperCategories = response.data;
            //alert ($rootScope.SuperCategories);
            defer.resolve($rootScope.SuperCategories);
        }, function(error) {
            defer.reject("Some error");
        });
        return defer.promise;
    }

    $rootScope.GetSuperCategories();


});

How to fix this bug.

2
  • 1
    When you say there is nothing in the service, you mean undefined or []? If the later, that's because your promise is not resolved yet. Commented Apr 6, 2015 at 17:04
  • Its blank [].. Can you please help how to resolve promise. Commented Apr 6, 2015 at 17:06

3 Answers 3

3

NB: this is not directly answer to you question. I've just tried to rewrite your code a little bit in a way to utilize promises and factory more common way, may be it will give you some ideas what can be improved to make the async code work more predictably.

Controller calls factory to get the data, and when Promise is resolved you have you data in $rootScope.SuperCategories

 sampleApp.factory('SuperCategoryService', ['$http', '$q', function ($http, $q){

    var req = {
        method: 'POST',
        url: 'SuperCategoryData.txt',
        //url: 'http://localhost/cgi-bin/superCategory.pl',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' }//,
        //data: { action: 'GET' }
    };

    var SuperCategoryService =  {};

    SuperCategoryService.SuperCategories = function () {

        var defer = $q.defer();
        $http(screq).success(function(response) {
            defer.resolve(response);
        });
        return defer.promise;
    }


    return SuperCategoryService;

}]);



sampleApp.controller('myController', ['$scope', 'SuperCategoryService', function($scope, SuperCategoryService) {

   SuperCategoryService.SuperCategories().then(function(data){
             $rootScope.SuperCategories = data;
        });


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

4 Comments

This is the correct way to do it. You are trying to time a data load event. There are two ways to do that but in both cases, the answer is a promise (like shershen has above). Otherwise, you are just preying what order your application bootstrap happens (and even then, you don't have a guarantee the data is there).
Also, $rootScope in this place is likely a bad idea. I would recommend the service hold that data.
In this case whenever controller will hit, it will call SuperCategoryService.SuperCategories() function, which internally makes a ajax request. i wanted to avoid that ajax request. so in app.run(), i was making the ajax request and trying to hold the data in $rootScope variable.
Why should controller hit several times? Unless it is router's controller, of course.
3

In such cases you could something like this.

sampleApp.run(function($rootScope, $q, $http) {
    ...  
    // once the promise is resolved and SuperCategories are filled  
    $rootScope.$emit('init');
});


sampleApp.controller('AppCtrl', function ($rootScope) {

    function init() {
        ...
    }

    var unbindHandler = $rootScope.$on('init', function () {
        init();
        unbindHandler();
    });

});

You can do similar thing with $rootScope.$watch, but since the watcher has initial launch, it isn't a good fit for one-time listeners.

Anyway, top-level controller is a better place for all initial work on the scope, there's no need to do something like this in run().

How to fix this bug.

It is not a bug but the expected behaviour of asynchronous workflow. You should never rely on delays when resolving promises, unless app gives you an opportunity to control its phases (and Angular doesn't give one).

Angular app runs in such order:

  • module config
  • module run
  • directive factory function
  • directive compile
  • directive controller (ng-controller is also a directive, right?)
  • directive prelink
  • directive (post)link
  • route resolve
  • route controller

Let's assume we monkey-patched $controllerProvider, so controllers will humbly wait for 'controllersReadySetGo' event from run() that may happen someday. Now guess what will happen with e.g. directive pre/postlinks that seriously rely on having a controller instance.

Comments

1

2 years later :-D

You could also wrap your controller logic into a $timeout of 0. This will execute your controller logic after the run block.

1 Comment

are you sure of this? I do not see this in the documentation of angular JS

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.