21

My problem is that i need a service loaded before the controller get called and the template get rendered. http://jsfiddle.net/g75XQ/2/

Html:

<div ng-app="app" ng-controller="root">
    <h3>Do not render this before user has loaded</h3>            
    {{user}}
</div>
​

JavaScript:

angular.module('app', []).
factory('user',function($timeout,$q){
    var user = {};            
    $timeout(function(){//Simulate a request
        user.name = "Jossi";
    },1000);
    return user;
}).
controller('root',function($scope,user){

    alert("Do not alert before user has loaded");
    $scope.user = user;

});
​

Jossi
  • 1,020
  • 1
  • 17
  • 28
  • I think it would be a lot easier if you just handle an unloaded state in the controller. Trying to get control over controller lifecycle seems to defeat the simplicity of angular model. – Guillaume86 Sep 30 '12 at 16:10

6 Answers6

14

You can defer init of angular app using manual initialization, instead of auto init with ng-app attribute.

// define some service that has `$window` injected and read your data from it
angular.service('myService', ['$window', ($window) =>({   
      getData() {
          return $window.myData;
      }
}))    

const callService = (cb) => {
   $.ajax(...).success((data)=>{
         window.myData = data;
         cb(data)
   })
}

// init angular app 
angular.element(document).ready(function() {
       callService(function (data) {
          doSomething(data);
          angular.bootstrap(document);
       });
});

where callService is your function performing AJAX call and accepting success callback, which will init angular app.

Also check ngCloak directive, since it maybe everything you need.

Alternatively, when using ngRoute you can use resolve property, for that you can see @honkskillet answer

vittore
  • 17,449
  • 6
  • 44
  • 82
  • 3
    No, `run` isn't the answer, as it's synchronous. Manual initialization is the only answer I've yet found that will resolve an async function whose output is needed app-wide. The question is: how do you address the properties of your services from the initialization function, so that you can store the output of the async call in something other than a global variable? – XML Aug 14 '13 at 13:29
  • @XMLilley actually no-how. there is ng-init but value from it is not accessible in `.config` so only global variables. – vittore Aug 14 '13 at 13:37
  • I'm a little confused about where to call this. Is this called within the ng-init where the ng-app directive is added? Or, is this a top-level angular call (like where one would call angular.module...)? – jbenowitz Sep 04 '13 at 15:08
  • Ignore that, needed to do more research. For those who need more help, this documentation is a good jump start (but like all angular documentation...it leaves one wanting more) http://docs.angularjs.org/guide/bootstrap – jbenowitz Sep 04 '13 at 15:15
  • @jbenowitz that is how you actually bootstrap your angular app. `angular.element.ready` runs passed function on DOMReady and function suppose to call your code which will do some async stuff and call `angular.bootstrap()` in the end – vittore Sep 04 '13 at 15:47
  • This shouldn't be the accepted responce. Use resolve property on the $routeProvider in .config to delay loading a route until an async task has been performed. – honkskillet Sep 10 '15 at 21:49
  • @honkskillet what you are offering is slightly different thing. what if you need to get something from the service BEFORE you are calling `.config` or `.run` – vittore Sep 10 '15 at 21:54
9

even better than manually bootstrapping (which is not always a bad idea either).

angular.module('myApp', ['app.services'])
   .run(function(myservice) {
      //stuff here.
   });
Prasanth Bendra
  • 31,145
  • 9
  • 53
  • 73
davidjnelson
  • 1,111
  • 12
  • 22
  • 6
    Is it possible to delay the app until a promise returned by run is being resolved or rejected? – Marc May 31 '13 at 15:00
  • 7
    This won't work with any asynchronous call. If a user hits F5 somewhere within the app, the service will most likely beat the app.run in its attempt to get to the data, causing errors. AngularJS desperately needs a universal resolve, a resolve that prevents any controller from executing until its promise returns. That would be perfect for absolutely necessary, app-wide data. – Tim Hardy Sep 25 '13 at 18:23
  • Interesting point Tim. I wonder if inlining data in the host page would solve that. – davidjnelson Jun 10 '15 at 22:29
4

As I said in the comments, it would be a lot easier to handle an unloaded state in your controller, you can benefit from $q to make this very straightforward: http://jsfiddle.net/g/g75XQ/4/

if you want to make something in the controller when user is loaded: http://jsfiddle.net/g/g75XQ/6/

EDIT: To delay the route change until some data is loaded, look at this answer: Delaying AngularJS route change until model loaded to prevent flicker

Community
  • 1
  • 1
Guillaume86
  • 14,341
  • 4
  • 53
  • 53
  • Ok I guess I will do something like that, but I hoped there was a way like https://www.youtube.com/watch?v=P6KITGRQujQ&feature=player_embedded but without the routes. – Jossi Oct 01 '12 at 01:27
  • ok actually you want to delay the location (route) change until some data is loaded, I don't know how to do this, you'll perhaps have more responses in the AngularJS google groups – Guillaume86 Oct 01 '12 at 08:42
  • this question looks like what're looking for: http://stackoverflow.com/questions/11972026/delaying-angularjs-route-change-until-model-loaded-to-prevent-flicker , you can define wich promises it should wait before loading the route – Guillaume86 Oct 01 '12 at 08:44
4

The correct way to achieve that is using resolve property on routes definition: see http://docs.angularjs.org/api/ngRoute.$routeProvider

then create and return a promise using the $q service; also use $http to make the request and on response, resolve the promise.

That way, when route is resolved and controller is loaded, the result of the promise will be already available and not flickering will happen.

Ciul
  • 633
  • 6
  • 7
  • Its very unfortunate that this is not the accepted answer, as it is the simplest approach without needing to manually boostrap on load. $q and promises are the way to go, whether you choose to use ngRoute, ui-router or even a controller on its own(say with ng-if based on a condition that is set upon promise resolution) – Vlad Gurovich Jan 19 '15 at 19:36
1

You can use resolve in the .config $routeProvider. If a promise is returned (as it is here), the route won't load until it is resolved or rejected. Also, the return value will be available to injected into the controller (in this case Somert).

angular.module('somertApp')
  .config(function($routeProvider) {
    $routeProvider
      .when('/home/:userName', {
        /**/
        resolve: {
          Somert: function($q, $location, Somert) {
            var deferred = $q.defer();
            Somert.get(function(somertVal) {
              if (somertVal) {
                deferred.resolve(somertVal);
              } else {
                deferred.resolve();
                $location.path('/error/'); //or somehow handle not getting
              }
            });
            return deferred.promise;
          },
        },
      });
  });
honkskillet
  • 3,007
  • 6
  • 31
  • 47
0

There are a few ways, some more advanced than others, but in your case ng-hide does the trick. See http://jsfiddle.net/roytruelove/g75XQ/3/

Roy Truelove
  • 22,016
  • 18
  • 111
  • 153
  • Im interested in a way that controller is also delayed until user has loaded. – Jossi Sep 30 '12 at 00:27
  • No not sure this is possible (or advisable?), but you can something close if you hold off on initing the whole app as vittore mentioned below. – Roy Truelove Sep 30 '12 at 13:33