How do I configure different environments in Angular.js?

How do I configure different environments in Angular.js?

How do you manage configuration variables/constants for different environments?
This could be an example:
My rest API is reachable on localhost:7080/myapi/, but my friend that works on the same code under Git version control has the API deployed on his Tomcat on localhost:8099/hisapi/.
Supposing that we have something like this :
angular
.module(‘app’, [‘ngResource’])

.constant(‘API_END_POINT’,’‘)

.factory(‘User’, function($resource, API_END_POINT) {
return $resource(API_END_POINT + ‘user’);
});

How do I dynamically inject the correct value of the API endpoint, depending on the environment?
In PHP I usually do this kind of stuff with a config.username.xml file, merging the basic configuration file (config.xml) with the local environment configuration file recognised by the name of the user. But I don’t know how to manage this kind of thing in JavaScript?

Solutions/Answers:

Solution 1:

I’m a little late to the thread, but if you’re using Grunt I’ve had great success with grunt-ng-constant.

The config section for ngconstant in my Gruntfile.js looks like

ngconstant: {
  options: {
    name: 'config',
    wrap: '"use strict";\n\n{%= __ngModule %}',
    space: '  '
  },
  development: {
    options: {
      dest: '<%= yeoman.app %>/scripts/config.js'
    },
    constants: {
      ENV: 'development'
    }
  },
  production: {
    options: {
      dest: '<%= yeoman.dist %>/scripts/config.js'
    },
    constants: {
      ENV: 'production'
    }
  }
}

The tasks that use ngconstant look like

grunt.registerTask('server', function (target) {
  if (target === 'dist') {
    return grunt.task.run([
      'build',
      'open',
      'connect:dist:keepalive'
    ]);
  }

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

grunt.registerTask('build', [
  'clean:dist',
  'ngconstant:production',
  'useminPrepare',
  'concurrent:dist',
  'concat',
  'copy',
  'cdnify',
  'ngmin',
  'cssmin',
  'uglify',
  'rev',
  'usemin'
]);

So running grunt server will generate a config.js file in app/scripts/ that looks like

"use strict";
angular.module("config", []).constant("ENV", "development");

Finally, I declare the dependency on whatever modules need it:

// the 'config' dependency is generated via grunt
var app = angular.module('myApp', [ 'config' ]);

Now my constants can be dependency injected where needed. E.g.,

app.controller('MyController', ['ENV', function( ENV ) {
  if( ENV === 'production' ) {
    ...
  }
}]);

Solution 2:

One cool solution might be separating all environment-specific values into some separate angular module, that all other modules depend on:

angular.module('configuration', [])
       .constant('API_END_POINT','123456')
       .constant('HOST','localhost');

Then your modules that need those entries can declare a dependency on it:

angular.module('services',['configuration'])
       .factory('User',['$resource','API_END_POINT'],function($resource,API_END_POINT){
           return $resource(API_END_POINT + 'user');
       });

Now you could think about further cool stuff:

Related:  How to use lifecycle method getDerivedStateFromProps as opposed to componentWillReceiveProps

The module, that contains the configuration can be separated into configuration.js, that will be included at your page.

This script can be easily edited by each of you, as long as you don’t check this separate file into git. But it’s easier to not check in the configuration if it is in a separate file. Also, you could branch it locally.

Now, if you have a build-system, like ANT or Maven, your further steps could be implementing some placeholders for the values API_END_POINT, that will be replaced during build-time, with your specific values.

Or you have your configuration_a.js and configuration_b.js and decide at the backend which to include.

Solution 3:

For Gulp users, gulp-ng-constant is also useful combined with gulp-concat, event-stream and yargs.

var concat = require('gulp-concat'),
    es = require('event-stream'),
    gulp = require('gulp'),
    ngConstant = require('gulp-ng-constant'),
    argv = require('yargs').argv;

var enviroment = argv.env || 'development';

gulp.task('config', function () {
  var config = gulp.src('config/' + enviroment + '.json')
    .pipe(ngConstant({name: 'app.config'}));
  var scripts = gulp.src('js/*');
  return es.merge(config, scripts)
    .pipe(concat('app.js'))
    .pipe(gulp.dest('app/dist'))
    .on('error', function() { });
});

In my config folder I have these files:

ls -l config
total 8
-rw-r--r--+ 1 .. ci.json
-rw-r--r--+ 1 .. development.json
-rw-r--r--+ 1 .. production.json

Then you can run gulp config --env development and that will create something like this:

angular.module("app.config", [])
.constant("foo", "bar")
.constant("ngConstant", true);

I also have this spec:

beforeEach(module('app'));

it('loads the config', inject(function(config) {
  expect(config).toBeTruthy();
}));

Solution 4:

To achieve that, I suggest you to use AngularJS Environment Plugin: https://www.npmjs.com/package/angular-environment

Related:  Determine if a date is a Saturday or a Sunday using JavaScript

Here’s an example:

angular.module('yourApp', ['environment']).
config(function(envServiceProvider) {
    // set the domains and variables for each environment 
    envServiceProvider.config({
        domains: {
            development: ['localhost', 'dev.local'],
            production: ['acme.com', 'acme.net', 'acme.org']
            // anotherStage: ['domain1', 'domain2'], 
            // anotherStage: ['domain1', 'domain2'] 
        },
        vars: {
            development: {
                apiUrl: '//localhost/api',
                staticUrl: '//localhost/static'
                // antoherCustomVar: 'lorem', 
                // antoherCustomVar: 'ipsum' 
            },
            production: {
                apiUrl: '//api.acme.com/v2',
                staticUrl: '//static.acme.com'
                // antoherCustomVar: 'lorem', 
                // antoherCustomVar: 'ipsum' 
            }
            // anotherStage: { 
            //  customVar: 'lorem', 
            //  customVar: 'ipsum' 
            // } 
        }
    });

    // run the environment check, so the comprobation is made 
    // before controllers and services are built 
    envServiceProvider.check();
});

And then, you can call the variables from your controllers such as this:

envService.read('apiUrl');

Hope it helps.

Solution 5:

You could use lvh.me:9000 to access your AngularJS app, (lvh.me just points to 127.0.0.1) and then specify a different endpoint if lvh.me is the host:

app.service("Configuration", function() {
  if (window.location.host.match(/lvh\.me/)) {
    return this.API = 'http://localhost\\:7080/myapi/';
  } else {
    return this.API = 'http://localhost\\:8099/hisapi/';
  }
});

And then inject the Configuration service and use Configuration.API wherever you need to access the API:

$resource(Configuration.API + '/endpoint/:id', {
  id: '@id'
});

A tad clunky, but works fine for me, albeit in a slightly different situation (API endpoints differ in production and development).

Related:  ng-mouseover and leave to toggle item using mouse in angularjs

Solution 6:

Good question!

One solution could be to continue using your config.xml file, and provide api endpoint information from the backend to your generated html, like this (example in php):

<script type="text/javascript">
angular.module('YourApp').constant('API_END_POINT', '<?php echo $apiEndPointFromBackend; ?>');
</script>

Maybe not a pretty solution, but it would work.

Another solution could be to keep the API_END_POINT constant value as it should be in production, and only modify your hosts-file to point that url to your local api instead.

Or maybe a solution using localStorage for overrides, like this:

.factory('User',['$resource','API_END_POINT'],function($resource,API_END_POINT){
   var myApi = localStorage.get('myLocalApiOverride');
   return $resource((myApi || API_END_POINT) + 'user');
});