Properly Set Environment Variables for Angular Apps with gulp-ng-config

Generating angular environment variables is immensely easy to do in this step by step tutorial.

Free Course

Getting Started with Angular 2

Angular 2 is the shiny new framework that comes with a lot of new concepts. Learn all the great new features.

Configuring environment variables (env vars for short) in Angular is now much easier to do. In this tutorial, we shall set environment variables for an AngularJS app using gulp-ng-config.

This tutorial is relatively easy to follow, if you have had experience with environment variables, Gulp or AngularJS.

Why You Should Use Environment Variables

Using env vars enables you to separate your source code from your application configuration (or config, for short). This is good practice because config varies substantially across your app deploys, but your code generally does not.

Some benefits you reap by employing env vars are:

  1. You can dynamically assign env vars, depending on the different types of environments your app resides in. The needs across your environments(development, staging, production e.t.c.) greatly differ. Hence, they require unique configs. For example, your development environment may use localhost for its api, while your production uses a live api hosted on a remote server.
  2. Switching between one environment and another is hassle-free, by just changing the name of your environment. For instance, on your continuous integration service, your env may be testing, while on you hosting provider, it changes to production.
  3. Sharing your code publicly or making it open source is easily achieved, without compromising your application's security. Sensitive credentials and resource details are safely stored away in your env vars, while your code is on a public repository.
  4. Scaling and configuring your application can be done automagically. Having a static config greatly limits how much your application can scale. If your config is separate from your code, changes can be made to the config, to scale the number of resources your application consumes, depending on its load.

What can you put into an environment variable?

Contents of your config/env vars may include things like:

  1. External API URLs that your app queries e.g. Google Maps API
  2. Credentials e.g. Amazon S3 secret keys and access ids
  3. Resources e.g. build pack url
  4. Deploy specific values e.g. whether to enable deletion or debug logging, the environment type

Now that we have got the basics down on environment variables, we can now write our first .env file.

Prerequisites

To start on this project you will have to have the folowing installed:

Scaffolding Our App

We need to lay the ground work for our app first. The contents of our project will include:

├── ./.bowerrc
├── ./.env
├── ./.gitignore
├── ./app
│   ├── ./app/app.less
│   ├── ./app/head.jade
│   ├── ./app/index.jade
│   └── ./app/scripts
│       ├── ./app/scripts/application.js
│       ├── ./app/scripts/config.js
│       ├── ./app/scripts/controllers.js
│       └── ./app/scripts/services.js
├── ./bower.json
├── ./config.js
├── ./gulpfile.js
├── ./index.js
└── ./package.json

We shall start by creating the:

  1. package.json: Run $ npm init and follow all the required steps to completion. The name of our app is ngEnvVars.
  2. bower.json: Run $ bower init and follow all the steps to completion using the same app name as above.
  3. .bowerrc: This will specify to bower where to store all the bower components it installs. Its contents include:
{
  "directory": "./public/lib"
}

Writing your .env File

A .env file is made up of environment variables, one on each line, that take this format:

ENV_VAR_NAME=env_var_value

Create a new folder named angular-env-vars on your terminal, change the directory to it and create your .env file. The contents of your .env file should look something like this:

API_URL=https://myawesomeapi.com
API_TOKEN=myawesomeapitoken
APP_DEBUG=false
APP_ENV=development

Keep your .env File Private

The contents of a .env file are quite sensitive and should hence be kept private. Be sure to leave it out of your version control system.

You do this by adding it in your .gitignore. This is a small sample of the contents of a .gitignore, but a more detailed file can be found here.

# Config Files
.env

# Runtime data
node_modules/
public/

Installing Our Dependencies

To create our sample app, we shall use jade and less, which make writing HTML and CSS much easier. Our app shall require a few packages to run:

  • browser-sync: to create the serve that will host our app
  • browserify: to bundle our different angular modules together
  • dotenv: to load our environment variables
  • gulp: the task runner that automates some of our jobs
  • gulp-bower: installs our bower components
  • gulp-jade: converts our jade files to HTML
  • gulp-less: converts our less files to CSS
  • gulp-ng-config: creates our angular environment constants
  • vinyl-source-stream: to aid browserify

Let's now install them.

$ npm install browser-sync browserify dotenv gulp gulp-bower gulp-jade gulp-less gulp-ng-config vinyl-source-stream --save

In addition to the above, we will need to install angular, angular material as our UI framework and font-awesome for our icons, using bower.

$ bower install angular angular-material font-awesome --save

We can now get to writing the code.

Env Vars for Different Environments

Some of your environment variables are shared between your various environments, but there are others that are unique to a particular environment. For instance, on your production environment, may require a BUILDPACK_URL to be able to build our app. Locally, however, we don't need it.

We can group relevant env vars of a particular environment together. To do this, we will create a file that returns an object with each environment's env vars.

Create a file called config.js on the base directory. We shall use this file to generate the config.json file that gulp-ng-config takes in as input.

// Shared env vars in all environments 
var shared = {
  apiUrl: process.env.API_URL || "http://localhost:3000/api",
  apiToken: process.env.API_TOKEN,
  debug: process.env.DEBUG || true
};

// 
var environments = {
  development: {
    ENV_VARS: shared
  },
  staging: {
    ENV_VARS: shared
  },
  production: {
    ENV_VARS: shared
  }
};
environments.production.buildpack  = process.env.BUILDPACK_URL;

module.exports = environments;

The Gulp File

Gulp will allow use to automate tasks like start our server and bundle our angular files together. Our main use for gulp here, is to load our env vars from the .env and generate our angular env constants.

First, we'll create a gulpfile.js in the base directory. Within it we shall have six main tasks:

  • jade : to compile our jade to html
  • less : to compile our less to css
  • browser-sync : to spun our server
  • browserify : to bundle our modules
  • ng-config : to create our angular config
  • bower : to install our bower dependencies
  • watch : to watch for any changes we make and rebuild our app

In addition, we will add two other tasks that build on the above, namely build and the default task. For the sake of brevity, I will demo the loading of env vars and the creation of the angular env constants only. However, you can still check out the rest of the full gulp file.

// If the app environment is not set, we default to development
var ENV = process.env.APP_ENV || 'development';

// Here, we use dotenv  to load our env vars in the .env, into process.env
if (ENV === 'development') {
  require('dotenv').load();
}

// Our dependencies and paths of files we use
var gulp = require('gulp'),
  gutil = require('gulp-util'),
  source = require('vinyl-source-stream'),
  ngConfig = require('gulp-ng-config'),
  path = require('path'),
  fs = require('fs'),
  config = require('./config.js'),
  paths = {
    public: {
      path: 'public/',
      script: './public/js/',
      lib: './public/lib/'
    },
    app: {
      jade: ['!app/shared/**', 'app/**/*.jade'],
      styles: 'app/styles/*.+(less|css)',
      staticFiles: [
        '!app/**/*.+(less|css|js|jade)',
        '!app/images/**/*',
        'app/**/*.*'
      ],
      scripts: {
        app: './app/scripts/application.js',
        all: './app/scripts/**/*.js'
      }
    }
  };

/*
 *  We first generate the json file that gulp-ng-config uses as input.
 *  Then we source it into our gulp task.
 *  The env constants will be a saved as a sub-module of our app, ngEnVars.
 *  So we shall name it ngEnvVars.config.
 */
gulp.task('ng-config', function() {
 fs.writeFileSync('./config.json',
      JSON.stringify(config[ENV]));
  gulp.src('./config.json')
    .pipe(
      ngConfig('ngEnvVars.config', {
        createModule: false
      })
    )
    .pipe(gulp.dest('./app/scripts/'))
});

/*
 * Browserify bundles our Angular env constants, 
 * services and controllers together into one file.
 * So, ng-config has to run first, to generate the angular env contants.
 * We shall add ng-config as a dependency of browserify, so that it runs before it.
 */
gulp.task('browserify', ['ng-config'], function() {
  return browserify(paths.app.scripts.app).bundle()
    .on('success', gutil.log.bind(gutil, 'Browserify Rebundled'))
    .on('error', gutil.log.bind(gutil, 'Browserify ' +
      'Error: in browserify gulp task'))
    .pipe(source('application.js'))
    .pipe(gulp.dest('./public/js/'));
});

gulp-ng-config takes two parameters:

  • module name: Our name here is ngEnvVars.config where ngEnvVars is the name of the angular app, while config denotes the content of our module. You can, however, name it whatever you may like.
  • configuration: These are the options we would like to specify for the module. For example, here, we use createModule and make it false. It indicates that we will not want a new module created for the env constants, but instead, we will define it on our own later in the code. For a full list of options, you could use, check out the gulp-ng-config npm page.

We will save the resulting config file in app/scripts/.

Our Angular App

We will break up our angular app into four main parts:

  • The main angular app
  • The controller for our single page
  • The service that uses our env constants
  • The config file generated by gulp-ng-config.

These will all be under the app/scripts folder. In addition to this, we shall have our index.jade as our main page, head.jade to hold all our dependency and styling links and lastly, app.less for our styling.

These will be under the app folder. Jade and Less are pretty straight-forward, but if you would like to find out more about it, check out the jade and less references.

The resulting folder structure will be as follows:

app
├── app.less
├── head.jade
├── index.jade
└── scripts
    ├── application.js
    ├── config.js
    ├── controllers.js
    └── services.js

The Main Angular App

Only two things are involved in creating our main app. First, we define all our dependencies(services, controllers, configs). Then we define our app, with it dependencies, both defined or installed.

We will do this in the app/scripts/application.js file. We will add the config.js first, then the services.js and finally the controllers.js. This is because the config is used by the services and the services used by the controllers, and hence should be loaded in that order.

(function() {
  'use strict';
  // Sub-modules of the app
  angular.module('ngEnvVars.controllers', []);
  angular.module('ngEnvVars.services', []);
  angular.module('ngEnvVars.config', []);

  // Constants
  require('./config.js');

  // Services
  require('./services.js');

  // Controllers
  require('./controllers.js');

  // Definition of the ngEnvVars app and its dependencies
  window.app = angular.module('ngEnvVars', [
    'ngEnvVars.config',
    'ngEnvVars.controllers',
    'ngEnvVars.services',
    'ngMaterial'
  ]);
}());

How our Config File Will Look Like

If you have created your .env, config.js and gulpfile.js, we are now ready to generate our angular config file. Note that, the name of our angular config file will be the same as the name of the config.json file generated when we run gulp. Remember that this file will be saved in app/scripts/.

Let's now get to generating the file.

$ gulp ng-config

Running this command will generate the file app/scripts/config.js, which should look like this:

angular.module("ngEnvVars.config")
  .constant("ENV_VARS", {
    "apiUrl": "https://myawesomeapi.com",
    "apiToken": "myawesomeapitoken",
    "debug": true,
    "env": "development"
  });

A Service that Uses our Env Constants

So now that we have our env vars, we could use them in a service. We shall make a service that logs a message depending on whether APP_DEBUG has been set to true in the .env. Let's call this service log. It shall need 3 things to work:

  • $log: An angular service for logging messages to the console.
  • ENV_VARS: The constant in which our environment variables are stored.
  • $mdToast: An angular material toast service that we will use to inform the user, when debugging has been set to false.

The resultant service takes this form:

angular.module('ngEnvVars.services')
  .factory('log', ['$log', 'ENV_VARS', '$mdToast',
    function($log, ENV_VARS, $mdToast) {
      /* Calling this service returns a function that takes in 
       * the type os a message and the message itself
       */
      return function(type, msg) {
        /* If debug has been set to true, logging will be enabled.
         * Otherwise, a toast will be displayed informing the user that 
         * this feature is disabled for that particular environment
         */
        if (ENV_VARS.debug === true) {
          if (type) {
            $log[type](msg);
            $mdToast.showSimple('Just printed a ' + type +
              ' message to the console');
          } else {
            $mdToast.showSimple('You have to specify' +
              ' a message type first');
          }
        } else {
          $mdToast.showSimple('This environment is not ' +
            'configured to log anything at this time');
        }
      };
    }
  ]);

Finally, our Controller

Our config file has used in our service. Now it's time to use our service in our very simple controller.

angular.module('ngEnvVars.controllers')
  .controller('HomeCtrl', ['$scope', 'log', 'ENV_VARS',
    function($scope, log, ENV_VARS) {
        // This will contain our envars
      $scope.envVars = ENV_VARS;

      // This will log a message to the console
      $scope.log = function() {
        log($scope.logType, 'You have logged a ' +
          $scope.logType + ' message to the console');
      };
    }
  ]);

The View: the Icing on the Cake

I've broken down our view into two parts: the head and the body. Our head(app/head.jade) will contain our links and scripts, while the body(app/index.jade) will be host to our page components.

The head:

base(href='/')
meta(charset='utf-8')
meta(name='description' content='angular app that accesses environment variables.')

// Angular material style
link(rel="stylesheet" href="lib/angular-material/angular-material.min.css")
link(rel='stylesheet' href="lib/font-awesome/css/font-awesome.min.css")
link(rel='stylesheet' type='text/css' href='app.css')

// Angular material javascript dependencies
script(src="lib/angular/angular.min.js")
script(src="lib/angular-aria/angular-aria.min.js")
script(src="lib/angular-animate/angular-animate.min.js")
script(src="lib/angular-material/angular-material.min.js")
script(src="lib/angular-resource/angular-resource.min.js")

// Our scripts
script(src="js/application.js")

title ngEnvVars

The main file containing both the body and the head(included as a file) :

doctype html
html(lang="en" ng-app="ngEnvVars")
    head
        include head
    body.bg-color.teal(flex ng-controller="HomeCtrl")
        div(flex layout="row" layout-align="center center" style="height: 100%")
            div(flex layout="column" layout-align="center center")
                span.fa-stack.fa-5x.md-margin
                    i.fa.fa-circle.fa-stack-2x.text.yellow
                    i.fa.fa-cogs.fa-stack-1x
                p.md-display-1 Angular Env Vars
                p with
                p.md-title gulp-ng-config
                // We display alist of our env constants here
                md-list(flex)
                    md-divider.md-margin
                    md-list-item.md-2-line(ng-repeat="(key, value) in envVars" ng-click="null")
                        span.fa-stack.fa-lg.md-margin
                            i.fa.fa-circle.fa-stack-2x.text.orange
                            i.fa.fa-wrench.fa-stack-1x
                        .md-list-item-text.layout-margin
                            h3(layout="row")
                                b Env Var: 
                                p {{key}}
                            p {{value}}
                // We use our log service here
                md-select(ng-model="logType" aria-label="Log Types")
                    md-option(ng-repeat="type in ['log', 'info', 'warn', 'error', 'debug']") {{type}}
                md-button.bg-color.yellow(ng-click="log()") Log Some Error

All that's left to do is style our page in the app/app.less file.

@teal: color("#4ABDAC");
@orange: color("#FC4A1A");
@yellow: color("#F7B733");
@grey: color("#DFDCE3");
* {
  margin: 0;
}

html,
body {
  height: 100%;
}

.text {
  &.orange {
    color: @orange !important;
  }
  &.teal {
    color: @teal;
  }
  &.yellow {
    color: @yellow;
  }
  &.grey {
    color: @grey;
  }
}

.bg-color {
  color: white;
  &.teal {
    background-color: @teal;
  }
  &.orange {
    background-color: @orange;
  }
  &.yellow {
    background-color: @yellow;
  }
  &.grey {
    background-color: @grey;
  }
  &.transparent {
    background-color: transparent;
  }
}

We are now set to launch our app. We do this by running:

$ gulp

Running this command will automatically open the page in your default browser. This should lead to our awesome ngEnvVars page that should look like:

We now have access to the environment variables in the .env in our angular app. We have listed them on our page.

In your .env, set the APP_DEBUG to true at first. Run gulp and log a few messages to the console. To view the results on the console on Chrome, type Ctrl + Shift + I or Cmd + Opt + I on a mac. You should see something like:

You can latter set the APP_DEBUG to false. You could open another terminal tab and run:

$ gulp ng-config

This command will regenerate the app/scripts/config.js. Our watch task in gulp will then rerun the app. When we now try to log to the console using our button, we shall get this message as a toast:

This concludes our tutorial on angular environment variables.

Conclusion

Environment variables are important resources that yeild a lot of benefits when kept separate from the code. It is at times difficult to have access to them for use in an angular app, but with gulp-ng-config, obtaining them is a fairly easy process.

Hopefully, by the end of this tutorial, you will have a good grasp on how to do this.

Hannah Koske

Software developer at Andela. Proficient in MEAN stack (MongoDB, Express.js, AngularJS, Node.js). Currently learning Python and Go. Voracious reader and do-it-yourself project enthusiast. In my free time, I paint, knit and build furniture.