jvandemo.com

How to configure your AngularJS application using environment variables

Introduction

user

Jurgen Van de Moere

Follow @jvandemo

How to configure your AngularJS application using environment variables

Posted by Jurgen Van de Moere on .
Featured

How to configure your AngularJS application using environment variables

Posted by Jurgen Van de Moere on .

featureimage

Most AngularJS applications contain logic.

And thus most AngularJS applications typically have a config, containing things like:

  • the url of the API to communicate with
  • the base url of the application
  • whether or not to output debug logging
  • etc.

Until recently I used to store such a config in an AngularJS constant:

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

// Example configuration stored as constant
ngModule.constant('config', {  
  apiUrl: 'https://your-api.com',
  baseUrl: '/',
  enableDebug: true
});

so I could easily pass it around in my application using dependency injection:

ngModule.service('api', ApiService);

// Inject config so the api service does
// not have to be aware of the actual API url.
function ApiService(config){

  this.getUsers = function getUsers(){
    return $http
      .get(config.apiUrl + '/users.json')
      .then(function(response){
        return response.data;
      });
  };

}

// Inject dependencies
ApiService.$inject = ['config'];  

but this turned out to be a painful mistake.

Why this approach should be avoided

The crucial downside of this approach is that the config is part of the application code.

The application's config is considered everything that is likely to vary between deploys (staging, production, etc).

This introduces multiple drawbacks:

  • Deploying an application in different environments requires different "builds" of the code. Imagine that your application needs to connect to a different API when running in a staging environment. You now have to create a different build of your application code.
  • Code from private applications can not be made public. Not all applications are deployed publicly on the internet. Imagine that you hire an external consultant to work on your private application code. The consultant now has access to all private configuration data that is stored in your config.
  • Not scalable. Modern cloud-based hosting infrastructure can scale dynamically. A fixed config prevents deployments tools from dynamically scaling and configuring your application as needed.

These drawbacks painfully confirm the The Twelve-Factor App config rule that states that there should always be a strict separation of config from code.

How back-end engineers solve this

Back-end engineers have been facing the same problem for years.

They typically solve it by storing configuration details in environment variables. Then they read these environment variables from within their back-end applications. Problem solved!

Unfortunately front-end applications do not have access to these back-end environment variables.

So are we, front-end developers, doomed forever?

Fortunately not!

The front-end way

Before we have a look at actual code, let's quickly recap what we are trying to do.

Why are we doing this?

  • We want to be able to deploy our AngularJS application in different environments (staging, production, etc.) with different configurations without changing the AngularJS application code.
  • We want to be able to share our AngularJS application code with external parties at any given moment without leaking any confidential configuration details.

What should we do to accomplish this?

  • We need to extract all configuration details out of our AngularJS application

The only remaining question is how do we do that?

Time for code!

STEP 1: Simulating an environment

We already learned that back-end engineers use environment variables, so let's learn from their prior art and tackle the problem in a similar way.

Let's add a <script> element for a new file env.js to our index.html and make it load before our AngularJS application code:

<html ng-app="app">

  <head>

    <!-- Load environment variables -->
    <script src="env.js"></script>

    <!-- Load AngularJS application -->
    <script src="app.js"></script>
  </head>

  <body>
    ...
  </body>  

</html>  

The env.js file contains all our environment variables and looks like this:

(function (window) {
  window.__env = window.__env || {};

  // API url
  window.__env.apiUrl = 'http://dev.your-api.com';

  // Base url
  window.__env.baseUrl = '/';

  // Whether or not to enable debug mode
  // Setting this to false will disable console output
  window.__env.enableDebug = true;
}(this));

This will make a special (global) variable __env available in our browser window containing all configuration details for our application.

You can open the console in your browser and type window.__env to display it.

STEP 2: Loading the environment in AngularJS

Next we import the environment variables in our AngularJS app and make it available as a constant called __env:

// app.js

var env = {};

// Import variables if present (from env.js)
if(window){  
  Object.assign(env, window.__env);
}

// Define AngularJS application
var ngModule = angular.module('app', []);

// Register environment in AngularJS as constant
ngModule.constant('__env', env);

Now we can use AngularJS dependency injection to inject the environment variables in a config block to configure logging:

// app.js

function disableLogging($logProvider, __env){  
  $logProvider.debugEnabled(__env.enableDebug);
}

// Inject dependencies
disableLogging.$inject = ['$logProvider', '__env'];

ngModule.config(disableLogging);  

and in a service constructor function to configure our API service:

// app.js

ngModule.service('api', ApiService);

// Inject __env so we can access environment
// variables
function ApiService(__env){

  this.getUsers = function getUsers(){
    return $http
      .get(__env.apiUrl + '/users.json')
      .then(function(response){
        return response.data;
      });
  };

}

// Inject dependencies
ApiService.$inject = ['__env'];  

Thanks to AngularJS dependency injection we can now access the environment variables anywhere in our AngularJS application.

The difference with the initial version using the config constant is that the actual values in the __env constant are now imported from an external script and are no longer part of the AngularJS application code.

But wait, doesn't this just shift the problem to env.js?

No, the default env.js in your code repository can contain no values at all or the configuration values that are needed to develop the application locally (and that can safely be shared with any other external developer):

(function (window) {
  window.__env = window.__env || {};
  window.__env.apiUrl = 'http://localhost:8080';
  window.__env.baseUrl = '/';
  window.__env.enableDebug = true;
}(this));

The deployment team can then overwrite the env.js file during deployment with values that should be used for that specific deployment such as staging or production:

(function (window) {
  window.__env = window.__env || {};
  window.__env.apiUrl = 'http://production.your-api.com';
  window.__env.baseUrl = '/';
  window.__env.enableDebug = false;
}(this));

Configuring the application using env.js can now be done by the deployment team and does not require a rebuild of the AngularJS application.

Summary

By strictly separating all configuration details in env.js, our application can now be:

  • deployed to different environments (staging, production, etc.) with different configurations without changing the AngularJS application code
  • shared with external parties at any given moment without leaking any confidential configuration details

Which is exactly what we needed to accomplish.

You can find a working example right here.

Have a great one!

user

Jurgen Van de Moere

Front-end architect at The Force. Gymnast. Dad. Family man. Creator of Angular Express.