Reading configuration files in Angular 2

Update: Since Angular 2 final release, one can utilize APP_INITIALIZER. In a comment on this post, Fernando’s dist shows its usage to load the environmental configuration files.

Note: This article was written at the time of angular-2-beta version. There are now some major changes in the RC version & the below code will stuck, so I will try to write a new post for the latest version. Thanks!

Recently, I started to make a dashboard starter project in Angular 2. It was no surprise to find lack of community and contribution on this alpha release framework. Setting up the configuration is a part of nearly every project. Recent frameworks allow you to manage configuration which is shared across all environments and which is separate for each environment, like for “production”, “development” and so on. Node.js already provides a neat mechanism to read JSON files, you can simply require the JSON configuration files. Thinking in the similar pattern, I’ve tried to make my own way in which the configuration is managed and loaded in your Angular 2 app.

I am distributing this tutorial into 3 parts:

  1. Setting up: JSON Files and Config class to load these files.
  2. Pre-loading: by making a custom RouterOutlet.
  3. Usage: of the Config service in your app.

Here it goes;

1. Setting Up

Setting up JSON Files

Let’s start by making a config folder in your app directory. In my case, its /src/app/config/. Inside it, make a file called ‘env.json’ which will hold the current environment and shareable configuration like this:

{
“env”: “development”
}

Make another file ‘development.json’ in the same folder:

{
“apiUrl”: “/src/”,
“debugging”: true
}

Config Class

Make a file called ‘config.ts’ in the same folder. I am using typescript in this project that is why it’s extension is ‘.ts’. The Config class will serve 2 purpose; to load the required files and to retrieve the config variables.

import { Injectable } from ‘angular2/core’;
@Injectable()
export class Config {
 private _config: Object
private _env: Object
 constructor(private http: Http) {
}
 load() {
// json files will be loaded here
}
 getEnv(key: any) {
return this._env[key];
}
 get(key: any) {
return this._config[key];
}
};

You must be asking why there are 2 private properties _config and _env. And two public methods getEnv() and get(). The reason is, all shareable configuration i.e. ‘env.json’ data will be held in _env object and environment specific data will be in _config property, which then will be called by their correspondent methods.

Now we will write the code to load the required files, we are using angular 2's bult-in ‘http’ module to load our json files:

import { Observable } from ‘rxjs/Observable’;
import { Http } from ‘angular2/http’;

Since in Angular 2 http calls return an Observable object rather than a promise, we will be using Observable module to throw an error. Let’s define our load() method:

load() {
 return new Promise((resolve, reject) => {
   this.http.get(‘src/app/config/env.json’)
.map(res => res.json())
.subscribe((env_data) => {
     this._env = env_data;
     this.http.get(‘src/app/config/’ + env_data.env + ‘.json’)
     .map(res => res.json())
     .catch((error: any) => {
console.error(error);
return Observable.throw(error.json().error || ‘Server error’);
})
     .subscribe((data) => {
this._config = data;
resolve(true);
});
   });
 });
}

In the above code we are making 2 requests one after another; one to load env.json and second to load the required environment .json file. The wrapping of Promise and the the use of ‘resolve(true)’ above is important, we will that discuss later.

2. Pre-loading

Possible Solutions

Actually, what we want, is to load the required config files before any of your component is initialized (including you main AppComponent which is used for bootstrap) and to make sure all the config is available as a service across the app. I’ve tried several different ways to make this achieve in Angular 2 and found only one convenient solution among these:

  1. Load files inside bootstrap().then() function.
  2. Load files in a component’s @CanActivate annotation.
  3. Use custom RouterOutlet which will load the files prior to route activation.

Let’s dig into the working of each:

The problem in the 1st solution is that the bootstrap is resolved after the AppComponent’s ngOnInit() and constructor() functions get executed, thus not making config being available in your AppComponent. You might be thinking that why don’t we call the load() method in either ngOnInit or construction methods of AppComponent? Because when we do that, as the files are being loaded asynchronously your other component’s constructor method also gets called, which is what we actually want to restrict.

For the 2nd case, loading files inside @CanActivate can make your app flow a bit complex, because it gets called without any Dependency Injection in it and you also have to implicitly annotate every component other than AppComponent (@CanActivate is a route lifecycle hook which gets called upon navigation and since no router is usually attached to AppComponent we cannot use it for AppComponent).

Custom RouterOutlet

Hence by using a custom RouterOutlet, you are making sure that all the files are loaded and available prior to any kind of initialization of a component. Create a new file inside a similar folder /src/app/shared/directives/ and name it ‘custom-router-outlet.ts’:

import {Directive, Attribute, ElementRef, DynamicComponentLoader} from ‘angular2/core’;
import {Router, RouterOutlet, ComponentInstruction} from ‘angular2/router’;
import { Config } from “../../config/config”;
@Directive({
selector: ‘custom-router-outlet’
})
export class CustomRouterOutlet extends RouterOutlet {
publicRoutes: any;
private parentRouter: Router;
  constructor(_elementRef: ElementRef, _loader: DynamicComponentLoader,
_parentRouter: Router, @Attribute(‘name’) nameAttr: string, private _config: Config) {
super(_elementRef, _loader, _parentRouter, nameAttr);
this.parentRouter = _parentRouter;
}
  activate(instruction: ComponentInstruction) {
return this._config.load().then(() => { return super.activate(instruction) })
}
}

As you can see in the “activate()” method we are calling our ‘Config.load()’ method which we built previously which returns a Promise, then on ‘resolve’ it tells router parent to activate the current route.

Now update your AppComponent with this:

import { CustomRouterOutlet } from ‘./shared/directives/custom-router-outlet’;
@Component({
selector: ‘app-container’,
template: ‘<custom-router-outlet></custom-router-outlet>’,
directives: [CustomRouterOutlet],
})
export class AppComponent {}

3. Usage

Usage is pretty simple and easy, let’s build a sample component ‘/src/app/sample/sample.component.ts’:

import {Config} from ‘../config/config’;
@Component({
selector: ‘sample’
})
export class SampleComponent {
  constructor(private _config: Config) {
var url = _config.get('apiUrl')
console.log(url)
var env = _config.getEnv('env')
console.log(env)
}
}

More ideas:

  • You can use cache to store and read config, although http requests in angular have built-in cache by default (inspect in your browser’s developer console).
  • Config class can also be used as a registry, simply create a getter ‘getCustom(key)’ and a setter ‘setCustom(key, val)’. This will come handy when you have something to store as a config or constant then later use this across the app.

You can see the above working code at angular2-dashboard-starter project. Thank you, have a happy coding day!

Like what you read? Give Hasan Hameed a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.