AngularJS load service then call controller and render
Asked Answered
M

6

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;

});
​

Monacid answered 29/9, 2012 at 23:25 Comment(1)
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.Unbelief
F
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

Finned answered 30/9, 2012 at 0:28 Comment(7)
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?Extinguish
@XMLilley actually no-how. there is ng-init but value from it is not accessible in .config so only global variables.Finned
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...)?Dionedionis
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) docs.angularjs.org/guide/bootstrapDionedionis
@Dionedionis 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 endFinned
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.Kermit
@Kermit what you are offering is slightly different thing. what if you need to get something from the service BEFORE you are calling .config or .runFinned
L
9

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

angular.module('myApp', ['app.services'])
   .run(function(myservice) {
      //stuff here.
   });
Lorenza answered 4/3, 2013 at 18:29 Comment(3)
Is it possible to delay the app until a promise returned by run is being resolved or rejected?Belligerence
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.Pleura
Interesting point Tim. I wonder if inlining data in the host page would solve that.Lorenza
U
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

Unbelief answered 30/9, 2012 at 16:15 Comment(3)
Ok I guess I will do something like that, but I hoped there was a way like youtube.com/watch?v=P6KITGRQujQ&feature=player_embedded but without the routes.Monacid
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 groupsUnbelief
this question looks like what're looking for: #11972526 , you can define wich promises it should wait before loading the routeUnbelief
F
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.

Forgiving answered 9/9, 2013 at 14:57 Comment(1)
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)Unreal
K
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;
          },
        },
      });
  });
Kermit answered 10/9, 2015 at 21:39 Comment(0)
H
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/

Hormone answered 29/9, 2012 at 23:54 Comment(2)
Im interested in a way that controller is also delayed until user has loaded.Monacid
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.Hormone

© 2022 - 2024 — McMap. All rights reserved.