Server-side constants injection into Angular modules

How to pass configuration into AngularJS during server-side template rendering.

My current single page applications have most of the logic in the browser using AngularJs and limited amount of data injected dynamically via server-side templates. Often there are multiple constants that might be injected by the server into the page: environment, api urls, user object, etc. Let me show how to properly inject these constants into Angular modules without leaking global variables all over the place.

I am using Jade server-side templates with Express server. For clarity I will show single page with all JavaScript embedded inside. In practice, only the constants are embedded, and the rest of the JavaScript is included via external js files.

Bad: global constants object

I will start with all constants rolled into single global object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
doctype html
html(ng-app="myApp")
head
meta(charset='utf8')
title Angular Example
script(src='https://ajax.googleapis.com/ajax/libs/angularjs/1.2.13/angular.min.js')
body
div(ng-controller='myAppCtrl')
h2 AngularJs Constants Example
p App's API url <strong>{{apiUrl}}</strong>
script.
// injected constants
var Constants = {
apiUrl: '#{apiUrl}',
environment: '#{runEnvironment}'
};
// using constants
angular.module('myApp', [])
.controller('myAppCtrl', function ($scope) {
$scope.apiUrl = Constants.apiUrl;
});

Here the express server route with actual values to be injected:

1
2
3
4
5
6
app.get('/example', function (req, res) {
res.render('example', {
apiUrl: '/debug/api/',
runEnvironment: 'debug'
});
});

The page renders when I browse to http://127.0.0.1:3001/example

AngularJs Constants Example
App's API url /debug/api/

The page's source when inspected:

1
2
3
4
5
6
7
8
9
10
<script>
var Constants = {
apiUrl: '/debug/api/',
environment: 'debug'
};
angular.module('myApp', [])
.controller('myAppCtrl', function ($scope) {
$scope.apiUrl = Constants.apiUrl;
});
</script>

Everything is working as normal, but this design suffers from several flaws:

  • After a while object Constants becomes a dumping ground for all sorts of values, effectively becoming a singleton for the entire application. A red flag is when you see code like this: Constants = Constants || {}; This means someone is about to extend / override something.
  • It is unclear that module myApp depends on the global Constants object.
  • It is harder to unit test code that depends on global singletons.

Pass 1: inject specific constants via a module

Lets introduce a new module with the sole purpose of being a storage of constants specific to myApp

div(ng-controller='myAppCtrl')
  h2 AngularJs Constants Example
  p App's API url <strong>{{apiUrl}}</strong>
script.
  angular.module('myApp', ['myApp.Constants'])
    .controller('myAppCtrl', function ($scope, API_URL) {
      $scope.apiUrl = API_URL;
    });
script.
  angular.module('myApp.Constants', [])
    .constant('API_URL', '#{apiUrl}')
    .constant('ENVIRONMENT', '#{runEnvironment}');

Again, normally myApp would be in a separate external static javascript file, while myApp.Constants would be inlined javascript in the template.

This is much cleaner design:

  • It is clear which constants are used by myApp
  • There are no global or leaking variables
  • The constants are read-only: if I change something inside the controller', the values in myApp.Constants remain unchanged (more on this later)
  • During unit testing we can inject a mock myApp.Constants module, making the testing independent of the global state.

I find this design a good first step, and the only real down side is the number of arguments to the controller function: it is linear to the number of constants. I prefer to keep number of function arguments below 3.

Pass 2: inject single object

Lets inject all our constants into single object

script.
  angular.module('myApp', ['myApp.Constants'])
    .controller('myAppCtrl', function ($scope, Constants) {
      $scope.apiUrl = Constants.apiUrl;
    });
script.
  angular.module('myApp.Constants', [])
    .constant('Constants', {
      apiUrl: '#{apiUrl}',
      environment: '#{runEnvironment}'
    });

I prefer this pattern:

  • Keeps the controller function signature clean
  • I don't need to use capital letters to signify constants. Writing Constants.apiUrl makes it clear enough.

Its only flaw right now is that if passing an object in JavaScript mean passing by reference. Any object in the chain can modify its local Constants object changing the shared instance. For example:

script.
  angular.module('badApp', ['myApp.Constants'])
    .run(function (Constants) {
      Constants.apiUrl = 'nowhere';
    });

  angular.module('myApp', ['badApp', 'myApp.Constants'])
    .controller('myAppCtrl', function ($scope, Constants) {
      $scope.apiUrl = Constants.apiUrl;
    });
script.
  angular.module('myApp.Constants', [])
    .constant('Constants', {
      apiUrl: '#{apiUrl}',
      environment: '#{runEnvironment}'
    });

Because badApp module runs before myApp, it has a chance to change the apiUrl to nowhere. Luckily, it is easy to solve in modern browsers (> IE8).

Pass 3: inject single constant object

We can inject a frozen object using ES5 Object.freeze method.

script.
  angular.module('badApp', ['myApp.Constants'])
    .run(function (Constants) {
      Constants.apiUrl = 'nowhere';
    });

  angular.module('myApp', ['badApp', 'myApp.Constants'])
    .controller('myAppCtrl', function ($scope, Constants) {
      $scope.apiUrl = Constants.apiUrl;
    });
script.
  angular.module('myApp.Constants', [])
    .constant('Constants', Object.freeze({
      apiUrl: '#{apiUrl}',
      environment: '#{runEnvironment}'
    }));

What happens when badApp tries to change the property of the frozen object? It depends if there is use strict; statement somewhere in the scope. If not, the statement Constants.apiUrl = 'nowhere'; fails silently, but the execution continues, the Constants object is unchanged. That is why I advocate use strict; for all browser code: trying to change the frozen object will generate an exception:

script.
  'use strict';
  angular.module('badApp', ['myApp.Constants'])
    .run(function (Constants) {
      Constants.apiUrl = 'nowhere';
    });
// generates
Uncaught TypeError: Cannot assign to read only property 'apiUrl' of #<Object>

Update

We have changed our approach to more flexible and intuitive configuration using custom provider, see this blog post.