Build once, run anywhere

tl;dr: checkout the example project on github or run the docker image with docker run -e API_URL=your-url -p 80:80 superafroman/angularjs-dynamic-config

The scenario

You've just built this great app using AngularJS. It didn't take long - you iterated on the idea really quickly, demoing to people as you went from your laptop. Now you need to deploy it and you realise you need slightly different configuration for your production setup - it's got a different API URL. So you google the problem and find a number of solutions pointing to changing the settings when you build the app in different ways. This'll work, you need dev and production, so you go ahead and configure them. Sorted.

A week goes by and the app is doing great. So great your boss wants you to make a change. But now that it's live and getting some exposure you're a little worried about going from your laptop to production and decide you need to get a staging environment setup where you can do some live-like testing.

No problem, you jump back into your build configuration and add a staging task and some staging config. You make the change, build the app and get a few people on board to sanity check it.

All good, of course, you're a legend. But now you've got to push to production and you realise that means a re-build, because your configuration is managed at build time.

What's the problem?

Every time you build your application something could change. It can also become unmanageable to configure all your environments in one build file. What you really want is to be able to promote your proven builds through environments.

The key to building your app once is to externalise anything that could change. So we want to externalise our configuration. You can do this in a number of ways but I'm going to go with environment variables here to demonstrate the approach.

The approach

Just like the build-time approaches we add a new file, config.js that defines a 'configuration' module. This defines your configuration as constants of the module.

angular.module('configuration', [])
  .constant('API_URL', 'http://localhost:9000');

And we add this as a dependency to our app

angular.module('angularjsDynamicConfigApp',
  ['configuration']);

Now to get your configuration to change based on your environment variables you need to set up your server to process the config.js file before sending it back to the browser. With nginx you first need to expose the environment variables you care about to your config (added in the root of nginx.conf)

env API_URL;

Then you need to retrieve the environment variables so they can be used within your server config

perl_set $api_url 'sub { return $ENV{"API_URL"}; }';

And finally you rewrite the contents of config.js

location /scripts/config.js {
  subs_filter_types application/javascript;
  subs_filter ('API_URL'\s*,\s*)'.*' $1'$api_url' or;
}

You can take a closer look at the code over on github.