Nirav Gandhi bio photo

Nirav Gandhi

Codes, Creates, Cleans, Cooks, Cheers & Cares.

Senior Product Developer at F22 Labs, India

Email Twitter Facebook LinkedIn Instagram Github Stackoverflow

We normally intend to use our project across multiple environments. For a typical application, we need a local/development environment for development, staging/test environtment for testing/review and a production environment for our end users.

For all these environments, we have some sort of configuration file that pulls in relevant settings such as database config, mail server settings, 3rd part service api keys, etc. as per the environment its deployed on.

Its possible to do this on a server side application using environment variables set on the server. But how do you do this on a standalone frontend app?

Your server needs to inform your angular app about the environment and for this your angular app needs to make a first request to the server. But the frontend app has no clue which env api (dev/staging/prod) to hit to get the info. Its a catch22 situation.

Things could be different if your angular app is not a standalone entity and is being served by your backend service (eg angular app inside rails app). You could set the information in a data attr of html tag that could be later consumed by your Angular app. But, as we discussed in an earlier post, this architecture becomes a nightmare to maintain in the long run. So how we do let our standalone app know about the environment its being used for?

Solution

The main idea lies in setting the config when you build your app. If you have been using yeoman angular generator, you must be using grunt/gulp to build your application. It usually involves creation of a distribution folder through various tasks to concat, uglify, cdnify etc your source code. So we could introduce an additional task to pull in and set environment related configs while building this distribution folder. Say hello to grunt-ng-constant.

grunt-ng-constant

Its a plugin for dynamic generation of angular constant and value modules. It generates a module with dynamic constants that can be injected as dependency inside your app module. Check the snippet below.

// configuration.js generated by grunt-ng-constant
angular.module('Constants', [])
.constant('ENV', {
                    name:'development',
                    api:'http://localhost:3000/api/v1/',
                    stripeKey: 'test1234xyz'}
);

and inject this module as dependency inside your primary app module

// app.js -- your app module
angular.module('ToDoApp', [
  'ngResource',
  'ngCookies',
  'ngMessages',
  'Constants'
])

Install and setup grunt-ng-constant

Install it using npm

$ npm install grunt-ng-constant --save-dev

Once installed, open your Gruntfile.js and check if you already have this command.

require('load-grunt-tasks')(grunt);
// It automatically loads all grunt tasks

If not, you can use following command as mentioned in documentation

grunt.loadNpmTasks('grunt-ng-constant');

Now lets write a simple config task that can generate our Constants module.

ngconstant: {
      options: {
        space: '  ',
        wrap: '"use strict";\n\n {%= __ngModule %}',
        name: 'Constants',
        dest: 'app/scripts/configuration.js'
      },
      development: {
        constants: {
          ENV: {
            name: 'development',
            apiEndpoint: 'http://localhost:3000/api/v1/',
            stripeKey: 'testBlaBla1234Xyz'
          }
        }
      },
      staging: {
        constants: {
          ENV: {
            name: 'staging',
            apiEndpoint: 'https://ww.my-staging-app.com/api/v1/',
            stripeKey: 'testBlaBla1234Xyz'
          }
        }
      },
      production: {
        constants: {
          ENV: {
            name: 'production',
            apiEndpoint: 'https://www.my-prod-app.com/api/v1/',
            stripeKey: 'pk_live_12345678'
          }
        }
      }
    }

Refer documentation to understand available options. For our use case, we have used module name as Constants and the path where the module would be written app/scripts/configuration.js. You could also assign data to ENV from json files.

constants: {
          ENV: grunt.file.readJSON('app/development.json')
        }

Lets modify some grunt tasks to run the task we wrote.

1) You need to call ngconstant:development when we do grunt serve to serve the app locally. You just need to add this task as shown below.

grunt.registerTask('serve', 'Compile then start a connect web server', function(target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'ngconstant:development', //here
      'wiredep',
      'concurrent:server',
      'autoprefixer:server',
      'connect:livereload',
      'watch'
    ]);
  });

2) You need to modify your grunt build task by adding task to generate production constants.

// Add 'ngconstant:production'
  grunt.registerTask('build', [
    'clean:dist',
    'ngconstant:production', //here
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'concat',
    'ngAnnotate',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin',
    'htmlmin'
  ]);

3) Till now you might be using same task to build staging and production dist. But now you would need to call ngconstant:env based on the env you wish to build. So we will just duplicate our build task and just modify our task to use staging.

// Add new task
  grunt.registerTask('build-staging', [
    'clean:dist',
    'ngconstant:staging',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'concat',
    'ngAnnotate',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin',
    'htmlmin'
  ]);

Now we can build for staging using grunt build-staging

4) You will also need to make sure that your configuration.js file is referenced in index.html and its referenced under build:js block to consider it for other grunt tasks

<!-- build:js scripts/scripts.js -->
<script src="scripts/configuration.js"></script>
<script src="scripts/app.js"></script>
<!-- all other js files -->

<!-- endbuild -->

That is it. Fire up your app and you could use these constants inside your app. For instance, you could use it in your ToDoCrud factory as follows.

angular.module('ToDoApp')
 .factory('toDoCrud', function (ENV, $http) {
    var public_api = {};
    public_api.create = function(params){
      var url = ENV.api + 'todo';
      return $http.post(url, params);
    }
    return public_api;
 });

And this is how you get environment vars in your angular app. Might look like some work, but once you set it up, it becomes very easy to extend and use env vars in your app as required.

Keep building!