AngularJS – Startup time environment configuration

Starting to learn a web framework, doesn’t matter which platform – Spring, AngularJS, Grails, Rails etc. is easy. But once you go past the elementary-school level status, a question always looms – how to accomplish tasks the framework-way versus the non-framework way?

A seemingly simple stuff like configuring environment properties at runtime (startup), took me through internals of AngularJS. For server-side folks, the technique is pretty standard: use any of .properties, .xml, .yaml and then apply a built-in framework feature. For eg. in Spring, you can using the @ConfigureProperties, for Grails, use the Config.groovy and the framework will automatically place it in grailsApplication, similar to what Rails does. But how do you do it for a purely client side application (ie ‘static’ site, that relies only on rest calls from some other server)?

AngularJS allows a Module.constant() method, where you can just specify a Javascript object and DI-it where-ever you want. Neat. But often the config values are environment specific. One of the very nice solutions is to use grunt-replace and simply provide the environment specific json at build time and grunt replaces the config values for that build. One may argue that this is the correct way to do, because it provides consistency and tracebality in CI.

But in some cases the values may not be known ahead before build and is known only at application startup. So the question is what is the AngularJS way of loading the configuration file at startup? Would have been nice to have AngularJs look for a config.json properties file at root and automatically load and inject it into a variable like $env.

Obviously, AngularJS offers so much stuff – constant, value, service, factory, provider. I thought I need to stick the $http somewhere and load the config.json file and set it to AngularJS constant or value. Then inject the constant to any controller or service.

1. Inject $http into Module.constant()

Will not work, constant accepts only an object and not services. At this point of time, it is useful to get familiar with config and run phases of AngularJS. This stackoverflow question explains succinctly.

2. Inject $http into Module.config()

Will not work. You cannot inject a AngularJS service into config. You can inject only a provider into config.

var envconfig = angular.module('envconfig', []);

envconfig.config(['$http', function($http) {
var envConfig = {
restUrl: 'http://localhost:8080/rest/api/'
};

$http.get('/config.json').onSuccess(function(data) {
envConfig = data;
});
}]);

So, how about injecting a provider into config, and let the provider use the $http ?

3. Inject $http into the provider

Will not work. You cannot inject a service ($http) directly into a provider, like this:

envconfig.provider('EnvConfig', function($http) {
});

Though you can use a service within the provider, like this:

envconfig.provider('EnvConfig', function() {
var envConfig = {
restUrl: 'http://localhost:8080/rest/api/'
};

var loadConfig = ['$http', function($http) {
$http.get('/config.json').success(function(data) {
envConfig = data;
});
return envConfig;
}];

//This $get, is kinda confusing - it does not return the provider, but it returns the "service".
//In our case, the "service" is the environment configuration object
//The $get is called automatically when AngularJS encounters a DI.
this.$get = loadConfig;
});

If you want to further configure the Provider, you can do this. Notice that the “Provider” suffix must be appended EnvConfig.

envconfig.config(['EnvConfigProvider', function(EnvConfigProvider) {
//Do something with EnvConfigProvider
}]);

While this almost works, there is a problem: $http is an asynchronous call, so its not guaranteed that the data will be returned with the values read from the config.json. Very likely the local envConfig will be returned always.

To get around you have to make $http a sync call, which is not possible. So we go deeper into the underlying framework of AngularJS – jQuery.

4. Load config.json synchronously

In the provider, you can do the following:

envconfig.provider("EnvConfig", function() {
this.$get = function() {
var q = jQuery.ajax({
type: 'GET', url: '/config.json', cache: false, async: false, contentType: 'application/json', dataType: 'json'
});
if (q.status === 200) {
angular.extend(envConfig, angular.fromJson(q.responseText));
}
return envConfig;
}];
});

Notice, that the q.responseText is a string and is converted to Json object using angular.fromJson(). Also by using angular.extend, you can merge the local envConfig with the target one – this allows you to keep some default values that you dont want to immediately expose.

5. How about profiles?

If you have multiple profiles in the config.json, you can simply use the following technique:

this.$get = ['$location', function($location) {
var profile = $location.search().profile || 'default';
var q = jQuery.ajax({
type: 'GET', url: '/config.json', cache: false, async: false, contentType: 'application/json', dataType: 'json'
});
if (q.status === 200) {
angular.extend(envConfig, angular.fromJson(q.responseText)[profile]);
}

return envConfig;
}];

And your config.json is like:

{
  "development": { "restUrl" : "http://devserver/rest/api" },
  "qa": { "restUrl" : "http://qaserver/rest/api" }
}

And then call your url as http://localhost:9000/index.html?profile=development

While adding profile query string may not be recommended for production, if you are testing your UI against several backend instances, this might just help.

Advertisements

6 Responses to AngularJS – Startup time environment configuration

  1. Awesome article! I’ve been wrestling with how to solve this and your solution works perfectly. I’m curous, however, about this comment:

    And then, use Module.config():
    envconfig.config([‘EnvConfigProvider’, function(EnvConfigProvider) {
    EnvConfigProvider.loadConfig();
    }]);

    Two questions: 1) where would that need to be used; and 2) is it even necessary, given the comment in the prior code example that “The $get is called automatically when AngularJS encounters a DI.”?

    I know I’m not using that code block, and everything is working great.

    Thanks again for a great article!

    Liked by 1 person

  2. vasya10 says:

    @Lance, Glad it helped !

    You are correct about EnvConfigProvider.loadConfig() not being required with respect to above example. I was trying that before using the $get. I will correct it. Thanks for pointing out !

    Like

  3. hello, my $get is undefined and when I try to use my provider, $http does not get injected, what am I doing wrong?

    Like

    • vasya10 says:

      @Harold, if you try to inject $http into provider, it wont work. See Step 3. $http is a service and provider is a “config” phase activity in Angularjs. During config phase, $http is unknown. You can create a service within provider and inject $http into it.

      Like

  4. mkipfelsberger says:

    Hi, just to mention two alternative approaches for app initialization:
    1. Server-side configuration: assemble a js-file containing an angular provider on server-side, which gets injected into config during automatic startup. the (js-)file gets loaded from the server and executed in config. Voilà! (perhaps a way to avoid caching of this file is required. Any suggestions?)
    2. Manual bootstrapping and async loading of config data: Omit the ng-app directive (which triggers automatic bootstrapping). Initialize your module, load asynchronously whatever you like and then bootstrap angular manually (see: https://docs.angularjs.org/guide/bootstrap)

    Liked by 2 people

  5. I was dealing with the same issues the other week and was glad that I found this post. Having a background primarily in Java back-end development, I expected a new framework such as AngularJS to have elegant solutions for configuration management, but unfortunately, it’s primitive compared to what Spring / Java EE has to offer. Defining configurations at build-time isn’t feasible, and requiring admins to edit some JS files somewhere deep in the application isn’t great. Since Spring is what I use in the back-end and it already solves configuration management issues, I’m using it with a REST interface which exposes the configurations needed by my AngularJS front-end.

    Not being used to the wide-spread use of asynchronous calls in AngularJS, I really wanted to be able to just fetch the configuration from the back-end once and then construct my Angular HTTP resources afterwards (their endpoint base-URLs are configurable). I got your last solution working, but I noticed that jQuery (2.2.1) prints a loud warning in the console:

    “Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user’s experience. For more help, check https://xhr.spec.whatwg.org/.”

    So, since that approach is deprecated and probably won’t work in the future, I decided to man up and just get used to dealing with JS promises. There isn’t really a way around it anyway.

    Liked by 1 person

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: