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.