Ionic2/Angular2 - Read a custom config file

I'm working on an ionic2 project and I need to create a new custom JSON config file. I found some tutorials to create one and access it through http.get but I think it's weird to call it through a get request. I want it in the root folder (where all the config JSONs are) and I open/read the file directly.

I don't know if it's possible, or even recommended ? This is why I'm posting here to have some opinions and solutions :)

Thanks


Solution 1:

Personally I don't like the read the config.json file by using the http.get way to handle configuration information, and even though there must be another way to just include and read the json file in your code, since we're using Angular2 and Typescript, why not using classes, interfaces and doing it in a more fancy way?

What I'll show you next may seem more complicated than it should at first (although after reading it you will find it very straightforward and easy to understand), but when I started learning Angular2 I saw an example of how they handled config files in the Dependency Injection guide and I followed that in the apps I've worked on to handle config information (like API endpoints, default values, and so on).

According the docs:

Non-class dependencies

[...]

Applications often define configuration objects with lots of small facts (like the title of the application or the address of a web API endpoint) but these configuration objects aren't always instances of a class.

One solution to choosing a provider token for non-class dependencies is to define and use an OpaqueToken

So you would need to define a config object with the urls and so on, and then an OpaqueToken to be able to use it when injecting the object with your configuration.

I included all my configuration in the app-config.ts file

// Although the ApplicationConfig interface plays no role in dependency injection, 
// it supports typing of the configuration object within the class.
export interface ApplicationConfig {
  appName: string;
  apiEndpoint: string;
}

// Configuration values for our app
export const MY_CONFIG: ApplicationConfig = {
  appName: 'My new App',
  apiEndpoint: 'http://www...'
};

// Create a config token to avoid naming conflicts
export const MY_CONFIG_TOKEN = new OpaqueToken('config');

What OpaqueToken is may be confusing at first, but it just a string that will avoid naming conflicts when injecting this object. You can find an amazing post about this here.

Then, you just need to include it in the page you need it like this:

import { NavController } from 'ionic-angular/index';
import { Component, OpaqueToken, Injectable, Inject } from "@angular/core";

// Import the config-related things
import { MY_CONFIG_TOKEN, MY_CONFIG, ApplicationConfig } from 'app-config.ts';

@Component({
  templateUrl:"home.html",
  providers: [{ provide: MY_CONFIG_TOKEN, useValue: MY_CONFIG }]
})
export class HomePage {

  private appName: string;
  private endPoint: string;

  constructor(@Inject(MY_CONFIG_TOKEN) private config: ApplicationConfig) {
    this.appName = config.appName;
    this.endPoint = config.apiEndpoint;
  }
}

Please notice how to include it in the providers array

providers: [{ provide: MY_CONFIG_TOKEN, useValue: MY_CONFIG }]

And how to tell the injector how it should obtain the instance of the config object

@Inject(MY_CONFIG_TOKEN) config: ApplicationConfig

UPDATE

OpaqueToken has been deprecated since v4.0.0 because it does not support type information, use InjectionToken<?> instead.

So instead of these lines:

import { OpaqueToken } from '@angular/core';

// Create a config token to avoid naming conflicts
export const MY_CONFIG_TOKEN = new OpaqueToken('config');

Now we should use

import { InjectionToken } from '@angular/core';

// Create a config token to avoid naming conflicts
export const MY_CONFIG_TOKEN = new InjectionToken<ApplicationConfig>('config');

Solution 2:

After reading and reading different solutions I ended up using this hacky implementation. Hopefully there will be a nice and native solution available soon:

import { NgModule } from '@angular/core';
import { environment as devVariables } from './environment.dev';
import { environment as testVariables } from './environment.test';
import { environment as prodVariables } from './environment.prod';

export function environmentFactory() {
  const location = window.location.host;
  switch (location) {
    case 'www.example.org': {
      return prodVariables;
    }
    case 'test.example.org': {
      return testVariables;
    }
    default: {
      return devVariables;
    }
  }
}

@NgModule({
  providers: [
    {
      provide: 'configuration',
      useFactory: environmentFactory
    }
  ]
})
export class EnvironmentsModule {}

and then where ever needed, e.g.:

import { Injectable, Injector, Inject } from '@angular/core';

import { AuthenticationService } from '../authentication';

@Injectable()
export class APIService {

  private http: Http;
  private apiURL: string;
  protected authentication: AuthenticationService;

  constructor(
    public injector: Injector,
    @Inject('configuration') public configuration: any
  ) {
    this.http = injector.get(Http);
    this.authentication = injector.get(AuthenticationService);
    this.apiURL = configuration.apiURL;    
  };

...