Asynchronously Bootstrapping AngularJS Applications with Server-Side Data

I recently worked on an AngularJS application that needs to be bootstrapped with some data from our server backend before it's being started. Pretty much all components of the application depend upon that server-side data, hence the data has to be accessible by the time the components are initialized.

If you want to bootstrap an AngularJS application with data from your backend, e.g. some essential configuration data, you basically have two main options. You can either …

  • embed the data in the HTML document, or
  • fetch it by making an additional HTTP request.

I've already written about how to inline .NET server-side data into the HTML response in Bootstrapping AngularJS Applications with Server-Side Data from ASP.NET MVC & Razor. This post is going to be about making an additional AJAX request to asynchronously fetch some server-side JSON data from a dedicated HTTP endpoint.

Automatically Bootstrapping an AngularJS Application

To initialize an AngularJS application, you would usually place the ng-app attribute on an HTML element which defines the application's scope. As soon as the DOM content has finished loading, Angular will take care of the setup process itself and will bootstrap the application:

<html ng-app="myApplication">
    <!-- ... -->
</html>

Unfortunately, this doesn't work with asynchronously loaded data which your application requires right from the start. Angular won't wait for your AJAX request to finish before starting the bootstrap process, so there's a high chance the data won't yet be loaded by the time the application is running.

Because it's very likely that the server-side data hasn't finished loading, your AngularJS controllers, directives, and other components have to be able to deal with missing data. You might find yourself writing guard clauses and checks for undefined all over your code base, if your application is able to work without the data at all. This is terrible!

Luckily, AngularJS applications can also be bootstrapped programmatically.

Manually Bootstrapping an AngularJS Application

Let's start by defining our application's main module:

var myApplication = angular.module("myApplication", []);

Now, instead of relying on the ng-app attribute, we can call the angular.bootstrap function manually. We need to hand it both the application root and the name of our main module. Here's how you call it as soon as the DOM has finished loading:

angular.element(document).ready(function() {
    angular.bootstrap(document, ["myApplication"]);
});

That should already be enough to get the application running. (Make sure to remove the ng-app attribute from your HTML!) We can now defer this initialization process until we've successfully grabbed the required data from the server. That will ensure that we won't have to worry about temporarily missing data.

Fetching the Required Data from the Server

We'll use Angular's $http service to make an AJAX request to the server. To use that service, we first have to get hold of the injector which usually performs dependency injection within the Angular infrastructure:

var initInjector = angular.injector(["ng"]);

Now we can resolve the dependency to $http like this:

var $http = initInjector.get("$http");

Let's now make the AJAX request to fetch the JSON data (configuration details, in my example) and store it in an Angular constant called config which we can access later within all of our controllers, services, and so on:

$http.get("/path/to/data.json").then(function(response) {
    myApplication.constant("config", response.data);
});

Et voilà, here's our required data, readily available to us. This is what our code looks like if we clean it up a little:

(function() {
    var myApplication = angular.module("myApplication", []);

    fetchData().then(bootstrapApplication);

    function fetchData() {
        var initInjector = angular.injector(["ng"]);
        var $http = initInjector.get("$http");

        return $http.get("/path/to/data.json").then(function(response) {
            myApplication.constant("config", response.data);
        }, function(errorResponse) {
            // Handle error case
        });
    }

    function bootstrapApplication() {
        angular.element(document).ready(function() {
            angular.bootstrap(document, ["myApplication"]);
        });
    }
}());

Note that we're returning a promise from the fetchData function so that we can chain the call to bootstrapApplication using then.

Warning, Here Be Dragons!

While the described approach works nicely, it doesn't come without some disadvantages. Think about how the browser loads the AngularJS application:

  1. A request to the initial HTML document is made (request #1).
  2. The document is returned. It references some JavaScript files.
  3. The referenced script files are loaded (request #2).
  4. Execution of the returned JavaScript files begins.
  5. Our script kicks off the AJAX request (request #3).
  6. The AJAX request returns with the required data.
  7. Finally, our AngularJS application is bootstrapped.

Notice that we're making three sequential HTTP requests until we can bootstrap our application. Depending on latency and bandwidth, that might result in a noticeable delay when loading the page.

Also, the boostrapping of the AngularJS application entirely depends on the AJAX request being successful. If the request fails, the application won't be initialized at all. You should consider this and implement a retry mechanism or provide some default data in case of a loading error.

Hope this helps, happy coding!

Related Posts

More AngularJS Material:

Use the coupon code LAUNCHDAY for $10 off!

Learn React

44 Comments

Ari Hershowitz

Perfect timing. I was looking for just such a solution. I considered oclazyload (loads modules, not assets or config data) and https://github.com/philippd/angular-deferred-bootstrap, which gets close. However, I wanted the app to bootstrap even if there is an error loading the config data. You mention this as a possible problem with your approach, but I am finding that the .then condition is met even when the response fails.

Rajesh Segu

Hi Marius, I just wrote a generic utility to manually bootstrap angular app after fetching server data.

https://github.com/rajeshsegu/angular-lazy-bootstrap

angular.lazy("demoApp")
  .resolve(['$http', function ($http) {
      return $http.get('/demo/api/config.json')
          .then(function (resp) {
              window.app.config = resp.data;
          });
      }])
   .loading(function(){
       angular.element('#loading').show();
   })
   .error(function(){
       angular.element('#error').show();
   })
   .done(function() {
       angular.element('#loading').hide();
   })
   .bootstrap();
yavorski

How do you deal with e2e testing and protractoor ?

Johannes

Good article. I had the same problem some time ago and most solutions pointed at the ng-route $routeProvider for doing a similar thing. Your solutions looks lightweight, I like it!

Scott Puleo

Great article Marius. You may want to make it a little clearer that manually bootstrapping the app in this fashion requires the removal of the ng-app attribute.

sumalatha

angular js returning full html page as response with $http.get(..).. pls help me

Vladimir Kelman

Marius,

Why do you use two different 'ready' methods, jQuery one and Angular one? Why not to wrap everything into angular.element(document).ready(function() {} and not to remove that 'ready' wrapping from inside of function bootstrapApplication() {} ?

Marius Schulz

Vladimir: I kickstart the AJAX request immediately, even before angular.ready is called, to retrieve the data as soon as possible. There's no need to delay that until the document is fully parsed and the ready event is fired.

Vladimir Kelman

You're right, my bad. You don't use jQuery ready, you perform Ajax request immediately.

Vladimir Kelman

I have a strange feeling that promises are not available inside (function() { . . . }());

I double-checked that $http and $http.get are defined, so that I used injector properly. I checked that myPath in $http.get(myPath) call is correct. I added both event handlers functions into then() part and put breakpoints into both.
Still after $http.get(myPath) is executed, neither event handler function is executed. Could it be that with Angular 1.2.10 this approach is not allowed?

Marius Schulz

Vladimir: The (function() { }()) wrapper is transparent to the contained code. It merely serves to prevent scope bleeding and thus shouldn't affect the execution of code inside it.

Vladimir Kelman

Marius, I understand that.

I just proved that this approach does not work with Angular v1.15 which we're currently using. It does work wit Angular v1.2+. It's a pity, because this approach look really clean and it is not easy to upgrade our big application to a latest Angular. With version 1.15 $http.get("/path/to/data.json").then( function1, function2) just never executes either success or error branch. It looks like earlier versions of Angular don't understand then until application is bootstrapped. After app is bootstrapped, inside regular controllers, etc. then works perfectly. To see it look at http://plnkr.co/edit/tVbq24 and replace Angular with earlier https://code.angularjs.org/1.1.5/angular.min.js

miller

sorry, for me this solution is working with a webapp but not with cordova...

Guillaume GARCIA

Hi,

I tried to mix both approaches (automatic bootstrapping and manual bootstrapping) for a "portal-like" application. The portal can be bootstrapped automatically but each portal service needs to be lazy-loaded "on-demand". Things that troubled me is that when you nested modules using automatic bootstrapping, there is no problem. But, when manually bootstrapping within an already automatically bootstrapped element (hope this is not too confusing...) I got the "Already bootstrapped" error. Any idea on this?

Here is a plunker to illustrate the problem. http://plnkr.co/edit/lWBcdB

Thanks in avance for your insight.

Priyanka Sethi

Thanks.

Prashant

Hey,

Gr8 article. I have an issue with this approach. I am trying to set my factory with the data I fetch after rest call. It gets set as well, but when I try to fetch the data in controllers, the data is again getting reset. Any ideas??

rmFactory.setData(response.data.data);
console.log('details in the Kickstarat',rmFactory.getData());

Factory :

'use strict';
(function () {
angular.module('deal.factories')
.service('deal.factories.RMDetailsFactory', RMDetailsFactory);
RMDetailsFactory.$inject = [];

function RMDetailsFactory() {


 var rmDetails={
   rmData :{
     name : null,
     salaryId : null,
     brand : null,
     subbrand : null,
     designation : null,
     location : null,
     manager : null,
     emailAddress : null,
     delegateEmailAddress : null,
     contactNumber : null,
     noOfActiveConnection : null,
     calLevel : null,
     calTraining : null
   },
   getData:getData_,
   setData:setData_
 };
return rmDetails;

function getData_(){
  return this.rmData;
}
function setData_(rmdetails){
 this.rmData= _.cloneDeep(rmdetails);

}
console.log('Rm details in the factory',rmData);

}})();
Anurag Singh

I have a question what happens to the jasmine test cases when we write the asynchronous bootstrapping. I am facing problem when I separate the bootstrap.js code with a app.js code . In bootstrap.js I have written the asynchronous bootstrapping logic and app.js contains the routeprovider logic. Apparently the app is working fine but jasmine testcases are failing stating that module is not avalible. Details are mentioned in stackoverflow : http://stackoverflow.com/questions/31233390/jasmine-test-not-working-if-i-bootstrap-angular-app-asynchronously

Jo Santana

Marius, just want to say that you saved my life! Thanks! ;)

Andrey

What the version of Angular do you use? Seems the code angular.constant("config", response.data); doesn't work. Did you mean angular.module('ng').constant("config", response.data);?

Marius Schulz

@Andrey: You're right, this was a typo. Fixed now, thanks!

Mark

During Bootstrapping after html page has been loaded a main module is loading and running. While running it gets tokens for authentication. When main menu becomes available I can click on it, load another module, and get data that is protected by tokens. At this time browser address bar changes to: main.html/item. But if at this point I refresh it by hitting F5 I get an error because a token is not available yet. Obviously, module 2 runs before the main module. Is there a way of running modules always in the initial order?

Thanks

Brett Morris

Marius,

I'm having issues with getting the default route to resolve after the manual bootstrap. Our app has grown and started as an automatically bootstrapped app, but we now need to get some configuration data from a service before we continue with initialising the app. So following your article above and added a $timeout to simulate loading. Everything seems to have loaded, I have a navigation pane which contains all the routes, If I click on them it redirects as it should, however the default view just doesn't seem to want to load.

Not sure what the problem might be. All the examples I have found online are very simple examples where most of the code is contained in a single page with script tags etc.

I'm now trying to do a redirection after the "angular.bootstrap" call to see if I can force the view/controller to be loaded.

BTW: I'm using the ng-view directive to load the views (if that helps/makes a difference).

Brett Morris

OK, my bad. I had mistakenly removed the app.run() code block away, thinking that would not be needed with manual bootstrapping. But it does.

Namal

I have a question. My app had a controller with ng-app. How can I use that too? <body ng-app="MyApp" ng-controller="baseController">

Johan

Excellent article, thank you for sharing!

Arnau

Interesting article Marius. How do you access the constant from your services later on?

Waqas

You sir, are genius. Thanks a lot for such an amazing solution. It has helped me a lot to get data before loading controllers. Keep up the good work :)

Marius Schulz

@Arnau: You simply retrieve it through Angular's dependency injection system, just like you would inject $http, for example!

angular
    .module("myApplication")
    .controller("TestController", TestController);

TestController.$inject = ["$http", "config"];

function TestController($http, config) {
    // ...
}
Arnau

It doesn't seem to work when the constant is in the main app, and you want to use it from one of its dependencies. The constant is not recognised by default, typical "Unknown provider" error, and if you add the main module as a dependency, then you are creating a cyclic dependency which doesn't work either.

Marius Schulz

@Arnau: In that case, you can create a separate configuration module that both your main app and its dependencies depend on!

Arnau

Ok, makes sense, thanks @Marius

Sangeeth VS

Have you tried unit testing with Jasmine & Karma on a manually bootstrapped Angular application. If so, can you please let me know how to do it?

Leah Towns

Agree with Sangeeth. Currently running into this problem with manually boostrapping the application and trying to unit test with Jasmine and Karma.

Yehuda

Hi, Thanks for interesting article.

if I want to save more the one constant record , what is your recommendation? Best, Yehuda

Robin

Hi there,

thanks a bunch, I liked this solution particularly because I was looking for a way to access the AngularJS components (e.g. $http) before Angular was bootstrapped.

I have one problem though, that I cannot get any of my own services/factories/providers through the injector. Any idea on how to do that? I tried all kinds of stuff, without success.

Cannot found anything on that topic online, maybe you can help.

Thanks

Shobhit jain

Hi Marius

its a nice article to asynchronously bootstrap an application. But I do have a Question i.e. how can I inject D.I. modules in my application.

Kindly help me...!!

Maksym Kobieliev

Got the same issue as Prashant.

I want to store the results of my ajax call in a service. Inside fetchData callback the data gets successfully written to a service, but inside my main controller the injector creates a new instance of the service, instead of returning the one already created before the bootstrap.

Any ideas on how to resolve this?

PS. The returned data can't be stored as a constant, because it is just an initial value, which is going to be changed throught the lifetime of the app.

Manish Singh

Hey! Marius

Helpful and Interesting article.

Have you tried unit testing with Jasmine & Karma on manually.

Steven

Hey Maksym,

There are a few ways you can save the data in your service.

I like doing it by creating a provider and saving the data in module.config. http://www.codeducky.org/angular-bootstrap-asynchronously walks your through loading data from a server, saving the data in a service, bootstrapping your app, handling errors, and displaying a loading UI while you wait for data to load from the server.

var yourApp = angular
    .module("YourApp", [])
    .provider("yourService", function () {
        var yourService = {
            data: null
        };
        this.setData = function (dataFromServer) {
            yourService.data = data;
        };
        this.$get = function() { return yourService; };
    });

function fetchData(dataFromServer) {
    var initInjector = angular.injector(["ng"]);
    var $http = initInjector.get("$http");

    return $http.get("/path/to/data.json").then(function(response) {
        yourApp.config(function (yourServiceProvider) {
            yourServiceProvider.setData(response.data);
        });
    }, function(errorResponse) {
        // Handle error case
    });
}
Julien

Hi Marius, I'm currently using your solution and it's working fine so thank you! I found that the unit tests (jasmine and karma) that we are doing are failing (saying that the config module isn't available) though. The only workaround I found was to create a fake config module to pass the tests then go back to the manual bootstrapping. Do you have a better solution? Thank you

AidaNow

Great article. Thank you very much. With a bit of change, worked perfectly for me.

Nikolay Evseev

Many thanks Marius, great solution to a commonly experienced problem. Originally I had it solved with the use of jquery ajax request on succession of which my app module was configured. This native angular solution is by far cleaner

Sheikh Razu

***** myapp.js

var app = angular.module('myApp', [ . . . 'myApp.services', . .

]);

init().then(bootstrapApplication);

function init() { var initInjector = angular.injector(["ng"]); var $http = initInjector.get("$http"); return $http.get("/api/abc/").then(function (response) { app.constant("ABC", response.data); console.log(response.data); }); };

function bootstrapApplication() { angular.element(document).ready(function () { angular.bootstrap(document, ["myApp"]); }); };

**** common.service.js

angular.module('myApp.services').service('CommonService', function($rootScope, $location, $window, ABC) {

function foo(){ } });


In my above code I am trying to use ABC constant in a service. And I am having the following error.

Unknown provider: ABCProvider <- ABC <- CommonService

Can anyone help me in this case?

Jason

if I only use $http before bootstrap, it works pretty well. While I also want to use some other service in my own module, when I use

var initInjector = angular.injector(["ng", "MyApp"]);

I see console log like following:

Uncaught Error: [$injector:unpr] Unknown provider: $rootElementProvider <- $rootElement <- $location ...

Is there anything I can do to workaround this?

Thanks.