Update
Please consider using method specified in the ?Asynchronously Bootstrapping AngularJS Applications with Server-Side Data? article if possible.
You can use the angular-deferred-bootstrap module to achieve that now!
I'm not sure about validity of this answer anymore, you can still use the ideas, but be sure to properly test it with your actual code. I will try to keep this answer up to date with never technologies.
Old answer
There are several approaches to the problem of asynchronous application initialization.
When it comes to data that must be resolved before a single controller is called - you can easily use resolve
option of ngRoute
's $routeProvider
. However, when you need some global data to be loaded before ANY controller is called - you have to improvise.
I've tried to collect all possible solutions in this answer. I'm providing them in the order of preference.
1. Using ui-router
When using ui-router instead of native ngRoute
you can create an abstract root state and resolve all data in it, before sub-states are activated.
I would recommend to use this approach. ui-router
provides a lot of additional features including ability to resolve dependencies hierarchically and is well accepted by the developer community.
Example
module.config(function($urlRouterProvider, stateHelperProvider) {
$urlRouterProvider.otherwise('/404');
stateHelperProvider.setNestedState({
name: 'root',
template: '<ui-view/>',
abstract: true,
resolve: {
user: function(UserService) {
// getCurrentUser() returns promise that will be resolved
// by ui-router before nested states are activated.
return UserService.getCurrentUser();
}
},
children: [{
name: 'index',
url: '/',
templateUrl: '/partials/index'
}, {
name: 'not-found',
url: '/404',
templateUrl: '/partials/404'
}, {
name: 'balance',
url: '/balance',
templateUrl: '/partials/balance',
resolve: {
balance: function(UserService, user) {
// Using data resolved in parent state.
return UserService.getBalanceByAccountId(user.accountId);
}
}
}]
});
});
The stateHelper
will help greatly to reduce the code when using abstract root scope approach.
Root scope is defined as abstract so can not be activated directly and it has no URL.
template: '<ui-view/>'
is required for nested views to be properly rendered.
2. Making promises in root controller
You can make promises and add them to the $rootScope
inside of your root controller, i.e. run()
function.
I've created a Plunk to demonstrate the idea:
http://plnkr.co/edit/gpguG5Y2S4KOz1KOKzXe?p=preview
This is a perfectly working solution, however, it bloats the code and makes it harder to use and understand (callback hell). I would recommend it only if the first approach is not working for you.
3. Passing data with the application page
You can include all initialization data directly to the HTML page generated on the server and access it from your application.
Consider this example:
<html>
<body>
<script src="application.js"></script>
<script type="text/javascript">
application.init({
// Pass your data here.
userData: { ... }
});
</script>
</body>
</html>
And you can bootstrap AngularJS application manually in the init()
method of your custom application
object.
I don't really like this approach, as I do believe that frontend and backend of Web application should be highly separated. Ideally, your frontend should be a static website (e.g. bunch of HTML, CSS and JS that can be delivered via CDN) and your backend should be a strictly an API server without a presentation layer (i.e. it should know nothing about HTML, CSS and such). However, it's a working solution if you can live with tight integration between application components.