Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Reading configuration files before application startup in Angular2 final release

Reading data before application startup in Angular 2

In this demonstration I will show you how to read data in Angular2 final release before application startup. You can use it to read configuration files like you do in other languages like Java, Python, Ruby, Php.

This is how the demonstration will load data:

a) It will read an env file named 'env.json'. This file indicates what is the current working environment. Options are: 'production' and 'development';

b) It will read a config JSON file based on what is found in env file. If env is "production", the file is 'config.production.json'. If env is "development", the file is 'config.development.json'.

All these reads will be done before Angular2 starts up the application.

It assumes you already have a working application, with a module and everything set up.

In Your Module

Open your existing module and add the following two lines to your list of providers.

import { APP_INITIALIZER } from '@angular/core';
import { AppConfig }       from './app.config';
import { HttpModule }      from '@angular/http';

...

@NgModule({
    imports: [
        ...
        HttpModule
    ],
    ...
    providers: [
        ...
        AppConfig,
        { provide: APP_INITIALIZER, useFactory: (config: AppConfig) => () => config.load(), deps: [AppConfig], multi: true }
    ],
    ...
});

The first line makes AppConfig class available to Angular2.

The second line uses APP_INITIALIZER to execute Config.load() method before application startup. The 'multi: true' is being used because an application can have more than one line of APP_INITIALIZER.

Make sure you set "HttpModule" in "imports" section if you want to make http calls using Angular2 built in Http library.

In app.config.ts

Create a class AppConfig and name the file 'app.config.ts' (you can use a name of your choice).

This is the place we will do the reading of env and config files. The data of both files will be stored in the class so we can retrieve it later.

Note that native Angular Http library is used to read the json files.

import { Inject, Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Rx';

@Injectable()
export class AppConfig {

    private config: Object = null;
    private env:    Object = null;

    constructor(private http: Http) {

    }

    /**
     * Use to get the data found in the second file (config file)
     */
    public getConfig(key: any) {
        return this.config[key];
    }

    /**
     * Use to get the data found in the first file (env file)
     */
    public getEnv(key: any) {
        return this.env[key];
    }

    /**
     * This method:
     *   a) Loads "env.json" to get the current working environment (e.g.: 'production', 'development')
     *   b) Loads "config.[env].json" to get all env's variables (e.g.: 'config.development.json')
     */
    public load() {
        return new Promise((resolve, reject) => {
            this.http.get('env.json').map( res => res.json() ).catch((error: any):any => {
                console.log('Configuration file "env.json" could not be read');
                resolve(true);
                return Observable.throw(error.json().error || 'Server error');
            }).subscribe( (envResponse) => {
                this.env = envResponse;
                let request:any = null;

                switch (envResponse.env) {
                    case 'production': {
                        request = this.http.get('config.' + envResponse.env + '.json');
                    } break;

                    case 'development': {
                        request = this.http.get('config.' + envResponse.env + '.json');
                    } break;

                    case 'default': {
                        console.error('Environment file is not set or invalid');
                        resolve(true);
                    } break;
                }

                if (request) {
                    request
                        .map( res => res.json() )
                        .catch((error: any) => {
                            console.error('Error reading ' + envResponse.env + ' configuration file');
                            resolve(error);
                            return Observable.throw(error.json().error || 'Server error');
                        })
                        .subscribe((responseData) => {
                            this.config = responseData;
                            resolve(true);
                        });
                } else {
                    console.error('Env config file "env.json" is not valid');
                    resolve(true);
                }
            });

        });
    }
}

See that we used resolve() in all scenarios because we don't want the application to crash if any problem is found in the configuration files. If you prefer, you can set error scenarios to reject().

In env.json

This is the place you will configure the current development environment. Allowed values are 'development' and 'production'.

{
    "env": "development"
}

You may add this file to .gitignore to your convenience.

In config.development.json

This is the place you will configure development config variables. You can add as many variables you want in this JSON file.

{
    "host": "localhost"
}

You may add this file to .gitignore to your convenience.

In config.production.json

This is the place you will write production config variables. You can add as many variables you want in this JSON file.

{
    "host": "112.164.12.21"
}

You may add this file to .gitignore to your convenience.

In Any Angular2 class

Example of how we read the values previously loaded from both files. In this case, we are reading the 'host' variable from config file and 'env' from the env file.

import { AppConfig } from './app.config';

export class AnyClass {
    constructor(private config: AppConfig) {
        // note that AppConfig is injected into a private property of AnyClass
    }
    
    myMethodToGetHost() {
        // will print 'localhost'
        let host:string = config.get('host');
    }
    
    myMethodToGetCurrentEnv() {
        // will print 'development'
        let env: string = config.getEnv('env');
    }
}
@patrickkee

This comment has been minimized.

Copy link

patrickkee commented Nov 5, 2016

It seems this only works for fetching data funfrom the backend over http. What about loading data from a global js variable in the browser?

@azulay7

This comment has been minimized.

Copy link

azulay7 commented Nov 29, 2016

Hi,
does anyone succeed to install it on angular-cli project?

@skgyan

This comment has been minimized.

Copy link

skgyan commented Nov 30, 2016

I am using http interceptor and while using this approach getting cyclic dependency. do you have any reference for the same?

@aryasunny

This comment has been minimized.

Copy link

aryasunny commented Dec 1, 2016

How would your load function in app.config.ts would come to know that where your env.json is located.
Ideally it should not be part of my source code. How would you mention that this file is located at some location external to the source code ??

In-fact I get compilation errors on switch (envResponse.env) because code is not able to find this env.json.

@jolmos

This comment has been minimized.

Copy link

jolmos commented Dec 2, 2016

@aryasunny: Your compile errors are caused by the compiler knowing nothing about the type of env. Add:

class EnvData {
   env: string;
}

Over
@Injectable()

and specify that envResponse is an EnvData with:
subscribe( (envResponse: EnvData)
instead of
subscribe( (envResponse)

@yogereddy

This comment has been minimized.

Copy link

yogereddy commented Dec 16, 2016

Thanks for great article. I'm stuck with this error

Unhandled Promise rejection: No provider for AppConfig! 

Any help would be appreciated.

@armno

This comment has been minimized.

Copy link

armno commented Jan 17, 2017

@Carpediemy I got the same error. It was that I forgot to add AppConfig itself into providers list.

@NgModule({
  ...
  providers: [
    ...
    AppConfig, // <---- this line
    { provide: APP_INITIALIZER, useFactory: (config: AppConfig) => () => config.load(), deps: [AppConfig], multi: true }
    ...
  ]
});
@PooperPig

This comment has been minimized.

Copy link

PooperPig commented Jan 18, 2017

I get an error
ERROR in Error encountered resolving symbol values statically. Function calls are not supported. Consider replacing the function or lambda with a reference to an exported function (position 33:46 in the original .ts file), resolving symbol AppModule in /app.module.ts

Help!

@ddramone

This comment has been minimized.

Copy link

ddramone commented Jan 18, 2017

@PooperPig functions and lambdas are not supported anymore in useFactory. You have to rewrite your code:

// Add this function
function initConfig(config: AppConfig){
 return () => config.load() 
}

@NgModule({
  ...
  providers: [
    ...
    AppConfig, 
    { provide: APP_INITIALIZER,
       useFactory: initConfig, // And use it here
       deps: [AppConfig], 
       multi: true } 
    ...
  ]
});
@Antarian

This comment has been minimized.

Copy link

Antarian commented Jan 22, 2017

Every envResponse.env after assigning this.env = envResponse; should be changed to this.getEnv('env') its better than create new EnvData class I think.

I also added in second subscribe type any. Instead of .subscribe((responseData) => { I have .subscribe((responseData: any) => {

Spent one day to get it up and running, learn much about http in Angular2. But still really useful gist. Thanks

@dbautistav

This comment has been minimized.

Copy link

dbautistav commented Jan 25, 2017

I'm stuck with a similar case using this strategy. I want to use the fetched configuration to inject it into an external library which I added into module's imports. How can I achieve that?

This is my AppModule:

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    ...,
    AngularFireModule.initializeApp(firebaseConfig),
  ],
  providers: [
    {
      deps: [ConfigService],
      multi: true,
      provide: APP_INITIALIZER,
      useFactory: configProvider,
    },
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

and this my ConfigService:

export function configProvider(cs: ConfigService) {
  return () => cs.load();
}

@Injectable()
export class ConfigService {
  public firebaseConfig;

  constructor(private http: Http) { }

  load(): Promise<FirebaseConfig> {
    const url: string = "https://somewhere.over/the/rainbow/firebaseConfigKeys.json";
    // I've updated this part of the code and actually returning the intended promise, but still doesn't work    :(
    const promise = this.http.get(url).map(res => res.json()).toPromise();
    promise.then(keys => { this.firebaseConfig = keys; });
    return promise;
  }
}

Any ideas? Thanks in advance.

@anyones

This comment has been minimized.

Copy link

anyones commented Jan 26, 2017

Although my AppConfig instance is successfully populated upon App initialization via the APP_INITIALIZER approach you suggested (as verified in the debugger, the response is correctly retrieved and config['host'] is defined), I'm not getting any data in AnyClass. The AppConfig instance I'm getting from the injector is empty, i.e. populated with the default values (null in this case). Any idea what I'm doing wrong? Also, could you please explain how concrete object allocation is handled by the injector? In your example, how does the injector know that you want the concrete object you loaded via APP_INITIALIZER injected into your AnyClass constructor (instead of just any new object of that class), where is the connection?
Thanks!

@elendil-software

This comment has been minimized.

Copy link

elendil-software commented Feb 2, 2017

@anyones
I noticed the same problem. I found that when I loaded pages with a component/service that need the appConfig, it was not set.
My mistake was :

constructor(private _http: Http, config: AppConfig) { this._apiEndpoint = config.apiEndpoint; }

With this code, the AppConfig was used before to be fully initialized, and this._apiEndpoint set with an empty default value.

I replaced this code with :

constructor(private _http: Http, private config: AppConfig) { }

And I'm just using this.config.apiEndpoint where I need it.

hope this helps.

@yogereddy

This comment has been minimized.

Copy link

yogereddy commented Feb 9, 2017

I'm able to get this working however my unit tests fail with 'Cannot read property of null'.
I have added the provider to test as well

beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ LoginComponent ],
      imports: [ReactiveFormsModule, RouterTestingModule, HttpModule],
      providers: [AuthService,
        SettingsService,
        AppConfig,
        {
            provide: APP_INITIALIZER,
            useFactory: initConfig,
            deps: [AppConfig],
            multi: true
        },
        ]
    })
    .compileComponents();
  }));

Any Idea? Thanks in advance

@fazanki

This comment has been minimized.

Copy link

fazanki commented Feb 10, 2017

APP_INITIALIZER used to work fine for me until I updated Angular to version 2.4.1.
My components and services are getting initialised before my ConfigServices using APP_INITIALIZER provider.

Does anybody else is experiencing the same problem?

providers: [ ConfigService, { provide: APP_INITIALIZER, useFactory: (config:ConfigService) => () => { return config.load(); }, deps: [ConfigService, Http], multi: true }, ],

@anyones

This comment has been minimized.

Copy link

anyones commented Feb 14, 2017

I ´ve checked my code several times, but I can´t get my mistake.
I still have the problem that my AppConfig instance is successfully populated upon App initialization via the APP_INITIALIZER approach.
BUT I'm not getting any data in AnyClass. The AppConfig instance I'm getting from the injector is empty, i.e. populated with the default values (null in this case).
Meanwhile I updated Angular 2 from version 2.0.0-rc5 to version 2.4.6 but the problem still persists.
Does anyone see my mistake?

AppModule:

//Add this function
function initConfig(config: AppConfig){
    return () => config.load()
}

@NgModule({
  imports:      [ BrowserModule, FormsModule, ReactiveFormsModule],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ],
  providers: [HttpModule, AppConfig, { provide: APP_INITIALIZER, useFactory: initConfig, deps: [AppConfig], multi: true }]  
})


export class AppModule { }

AppConfig:

@Injectable()
export class AppConfig {

    private config: Object = null;
    private env:    Object = null;

    constructor(private http: Http) {

    }

    /**
     * Use to get the data found in the second file (config file)
     */
    public getConfig(key: any) {
        return this.config[key];
    }

    /**
     * Use to get the data found in the first file (env file)
     */
    public getEnv(key: any) {
        return this.env[key];
    }

    /**
     *   a) Loads "env.json" to get the current working environment (e.g.: 'production', 'development')
     *   b) Loads "config.[env].json" to get all env's variables (e.g.: 'config.development.json')
     */
    public load() {
        return new Promise((resolve, reject) => {
            this.http.get('./app/conf/env.json').map( res => res.json() ).catch((error: any):any => {
                console.log('Configuration file "env.json" could not be read');
                resolve(true);
                return Observable.throw(error.json().error || 'Server error');
            }).subscribe( (envResponse) => {
                this.env = envResponse;
                let request:any = null;

                
                switch (envResponse["env"]) {
                    case 'production': {
                        request = this.http.get('app/conf/config.' + envResponse["env"] + '.json');
                    } break;

                    case 'development': {
                        request = this.http.get('app/conf/config.' + envResponse["env"] + '.json');
                    } break;

                    case 'default': {
                        console.error('Environment file is not set or invalid');
                        resolve(true);
                    } break;
                }
                
                if (request) {
                    request
                        .map( res => res.json() )
                        .catch((error: any) => {
                            console.error('Error reading ' + envResponse["env"] + ' configuration file');
                            resolve(error);
                            return Observable.throw(error.json().error || 'Server error');
                        })
                        .subscribe((responseData) => {
                            debugger;
                            this.config = responseData;
                            resolve(true);
                        });
                } else {
                    console.error('Env config file "env.json" is not valid');
                    resolve(true);
                }
            });

        });
    }
}

AnyClass:

@Injectable()
export class AnyClass {
    constructor(private http: Http, private config: AppConfig) {
        this.config = config; // <-- 'config' IS NULL!!!
    }...
	
	
	
	...	
	let host:string = this.config.getConfig('host'); // <-- 'this.config' IS NULL, because it is set to null in the constructor.

Thank you in advance!

@Sparkomatic

This comment has been minimized.

Copy link

Sparkomatic commented Feb 16, 2017

I got the example working just as descried. My problem is now, instead of using this.http.get and returning a promise, I am trying to do a similar thing using AngularFire with my method returning this.af.database.list('path') which is a FirebaseListObservable. My data is always undefined. Any ideas?

@skirdey

This comment has been minimized.

Copy link

skirdey commented Feb 22, 2017

for the most recent version of angular, this works:

function initConfig(config: AppConfig){
    return () => config.load()
}

change to

**export** function initConfig(config: AppConfig){
    return () => config.load()
}

and place it right above @NgModule decorator in the app.module.ts,

also, adjust providers to look like

... AppConfig,   { provide: APP_INITIALIZER, useFactory: initConfig, deps: [AppConfig], multi: true } ... 
@geekrumper

This comment has been minimized.

Copy link

geekrumper commented Feb 27, 2017

Like @anyones is mentioning, the values aren't set when the constructor of a service is being called. So instead of setting the config value inside the constructor of a service once (like putting up the pieces for a "basePath"), you have to get them on each service method. You end up with a lot of redundant code to get x amount of configuration variables. Thanks to @elendil-software for the workaround.

For e.g. my basePath contains three pieces: protocol | hostname | port (and one of my service classes contains 21 methods, you can do the math)

const protocol = this._config.getConfig(PROTOCOL);
const host = this._config.getConfig(HOST);
const port = this._config.getConfig(PORT);

this.basePath = `${protocol}://${host}:${port}`;

I could emit basePath and put the values directly, but it's still an overhead...

here is a console log, which is chronically showing what is happening:
config_load

Don't get me wrong, I really like this approach and I'm really thankful for the effort to get such a clean solution. I'm just sharing my experience.

@brianlittmann

This comment has been minimized.

Copy link

brianlittmann commented Feb 27, 2017

To get this working, I had to add some custom copy config to copy the *.json files to the www/ dir.

Create or update copy.config.js in your root dir:

module.exports = {
	copyEnvConfig: {
		src: ['env.json', 'config.*.json'],
		dest: '{{WWW}}'
	}
}

Then in package.json, add:

"config": {
	"ionic_copy": "./copy.config.js"
}
@philipjohnfob

This comment has been minimized.

Copy link

philipjohnfob commented Mar 1, 2017

I followed the steps mentioned in this gist. But when I add the AppConfig in the constructor, I get the following error in console.

EXCEPTION: Uncaught (in promise): Error: DI Error
Error: DI Error
    at NoProviderError.ZoneAwareError
.
.
.
Unhandled Promise rejection: No provider for AppConfig! ; Zone: angular ; Task: Promise.then ; Value:

My constructor looks like below.

  constructor(
    private http: Http,
    private cookieService: CookieService,
    private config: AppConfig) {
      this.authUrl = "https://test/test";
  }

I added the AppConfig in @NgModule providers section in the following order.

    AppConfig,
    { provide: APP_INITIALIZER, useFactory: (config: AppConfig) => () => config.load(), deps: [AppConfig], multi: true },
    CookieService,
    AuthService,

I see that the load() method is successfully executed. I see the config data while debugging. But I m not able to inject the AppConfig class into my service.
Any ideas how I can resolve this issue?

@philipjohnfob

This comment has been minimized.

Copy link

philipjohnfob commented Mar 1, 2017

NVM.
The issue was because of a silly error.
I gave the import in app.module.ts as
import { AppConfig } from './config//app.config';
instead of
import { AppConfig } from './config/app.config';

The // caused the issue. Between, thanks for this brilliant approach.

@Chuvisco88

This comment has been minimized.

Copy link

Chuvisco88 commented Mar 8, 2017

I have the same problem as @Carpediemy.
When doing tests I get Cannot read propery 'SomeProperty' of null.
Do we need to call the load function manually somehow or how to establish the config in testing?

@h4mit

This comment has been minimized.

Copy link

h4mit commented Mar 9, 2017

Hi, thank's for solution, but how to use getConfig in @NgModule file ?

@TRAHOMOTO

This comment has been minimized.

Copy link

TRAHOMOTO commented Mar 19, 2017

+1 many thx for solution, very helpful! Especially { provide: APP_INITIALIZER, useFactory: ...

@scottseeker

This comment has been minimized.

Copy link

scottseeker commented Apr 9, 2017

@Carpediemy @Chuvisco88 ever find a solution? It seems the APP_INITIALIZER doesn't work properly in tests. We had to just manually call config.load() in a beforeEach()...

@Priyesha

This comment has been minimized.

Copy link

Priyesha commented Apr 11, 2017

@scottseeker Yes, i am also trying to use APP_INITIALIZER to load config file in tests but its not working. How did you manually call config.load in beforeEach().
I am getting an error "Cannot read property load of undefined".

@angular2bb

This comment has been minimized.

Copy link

angular2bb commented Apr 18, 2017

I have the same problem as @anyones and the @geekrumper solution does not work for me.
I am wondering how to get the data load from the service that is APP_INITIALIZER in my component. As i found that when i add
constructor( private config: AppConfig) , the AppConfig is constucted once again so the data load from APP_INITIALIZER is lost.

Appreciated that if anyone can help with this.

@benjaminu

This comment has been minimized.

Copy link

benjaminu commented Apr 30, 2017

@dbautistav: Did you ever get an answer to your question please? I'm in the same situation as you were/are.

I'm stuck with a similar case using this strategy. I want to use the fetched configuration to inject it into an external library which I added into module's imports. How can I achieve that?

This is my AppModule:

@NgModule({
declarations: [
AppComponent,
],
imports: [
...,
AngularFireModule.initializeApp(firebaseConfig),
],
providers: [
{
deps: [ConfigService],
multi: true,
provide: APP_INITIALIZER,
useFactory: configProvider,
},
],
bootstrap: [AppComponent]
})
export class AppModule { }
and this my ConfigService:

export function configProvider(cs: ConfigService) {
return () => cs.load();
}

@Injectable()
export class ConfigService {
public firebaseConfig;

constructor(private http: Http) { }

load(): Promise {
const url: string = "https://somewhere.over/the/rainbow/firebaseConfigKeys.json";
// I've updated this part of the code and actually returning the intended promise, but still doesn't work :(
const promise = this.http.get(url).map(res => res.json()).toPromise();
promise.then(keys => { this.firebaseConfig = keys; });
return promise;
}
}
Any ideas? Thanks in advance.

@crh225

This comment has been minimized.

Copy link

crh225 commented May 4, 2017

@benjaminu what if you change you app moduel to look like

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    ...,
    AngularFireModule.initializeApp(firebaseConfig),
  ],
  providers: [
ConfigService,  //this is what I had to add
    {
      deps: [ConfigService],
      multi: true,
      provide: APP_INITIALIZER,
      useFactory: configProvider,
    },
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
@sarahAwad

This comment has been minimized.

Copy link

sarahAwad commented May 6, 2017

I'm stuck with this error, in the load method, it can't reach the file env.json file nor the development.json file
{ _body: " <h…", status: 404, ok: false, statusText: "Not Found", headers: Object, type: 2, url: "http://localhost:4200/app/config/de…" }

@yogereddy

This comment has been minimized.

Copy link

yogereddy commented May 8, 2017

@Chuvisco88, @scottseeker, @Priyesha were you able to find a solution for app initializer to work in unit tests?

@pushkalb123

This comment has been minimized.

Copy link

pushkalb123 commented Jun 5, 2017

@PooperPig: I am getting the same error and (ddramone) solution didnot work for me. Is the issue solved.
@h4mit : how to use getConfig in @NgModule file ? Did you get answer to this.

@creineq

This comment has been minimized.

Copy link

creineq commented Jun 7, 2017

It takes too long to load using
import { Observable } from 'rxjs/Rx';
Too many files inside Rx folder. So I did this to improve loading time.
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/catch'

@Jensiator

This comment has been minimized.

Copy link

Jensiator commented Jun 7, 2017

This made my load function to work

public load() {
        let promise =this.http.get('url').map(res => res.json()).toPromise();
        promise.then(config =>  this.validation = config);
        return promise;
    };
@tkir

This comment has been minimized.

Copy link

tkir commented Aug 2, 2017

Hi, thank for great tutorial!
If I use config deeper then one level, getConfig(key) returns undefined.
My solution:
public get(key: any) { let res:any=this.config; key.split('.') .forEach(k=>res=res[k]); return res; }

@deepakanuraags

This comment has been minimized.

Copy link

deepakanuraags commented Aug 10, 2017

why cant we simple have static variable in a class => so no injectable is required,simply import the class and read the static variable which will be same across whole of app,why do we have to do this much of code doing appinitializer and stuff,someone please explain so i can get a better understanding.

@Chili82

This comment has been minimized.

Copy link

Chili82 commented Aug 25, 2017

Unit test for Config

import { MockBackend, MockConnection } from '@angular/http/testing';
import { BaseRequestOptions, ConnectionBackend, Http, HttpModule, Response, ResponseOptions, RequestOptions } from '@angular/http';
import { async, fakeAsync, inject, TestBed } from '@angular/core/testing';
import { Config, MockConfig } from './config';

describe('Config', () => {
    beforeEach(() => {
      TestBed.configureTestingModule({
        imports: [HttpModule],
        providers: [Config, MockConfig,
            {
                provide: Http, useFactory: (backend: ConnectionBackend, defaultOptions: BaseRequestOptions) => {
                return new Http(backend, defaultOptions);
              }, deps: [MockBackend, BaseRequestOptions]
              },
          {provide: ConnectionBackend, useClass: MockBackend},
          {provide: MockBackend, useClass: MockBackend},
          {provide: BaseRequestOptions, useClass: BaseRequestOptions}],
      });
    });


it('should Config ...', fakeAsync(inject([Config, MockBackend], (conf: Config, mockBackend: MockBackend) => {
    expect(conf).toBeTruthy();
     conf.load();
     conf._config = { 'host': 'http://localhost'};
     conf._env = { 'env': 'development'};
    const host = conf.get('host');
    const env = conf.getEnv('env');
    expect(host).toEqual('http://localhost');
    expect(env).toEqual('development');

    let res: any;
    mockBackend.connections.subscribe(c => {
        const response = new ResponseOptions({ body: { env: 'development' }});
        c.mockRespond(new Response(response));
      });
      conf.load().then((response) => {
        res = response;
      });
})));

it('should not Be Config ...', fakeAsync(inject([Config, MockBackend], (conf: Config, mockBackend: MockBackend) => {
    let res: any;
    mockBackend.connections.subscribe(c => {
        const response = new ResponseOptions({ body: { envs: '123developments123' }});
        c.mockRespond(new Response(response));
      });
      conf.load().then((response) => {
        res = response;
      });
})));

it('should production Config ...', fakeAsync(inject([Config, MockBackend], (conf: Config, mockBackend: MockBackend) => {
    let res: any;
    mockBackend.connections.subscribe(c => {
        const response = new ResponseOptions({ body: { env: 'production' }});
        c.mockRespond(new Response(response));
      });
      conf.load().then((response) => {
        res = response;
      });
})));

it('should default Config ...', fakeAsync(inject([Config, MockBackend], (conf: Config, mockBackend: MockBackend) => {
    let res: any;
    mockBackend.connections.subscribe(c => {
        const response = new ResponseOptions({ body: { env: 'default' }});
        c.mockRespond(new Response(response));
      });
      conf.load().then((response) => {
        res = response;
      });
})));

// tslint:disable-next-line:eofline
});
@ridj87

This comment has been minimized.

Copy link

ridj87 commented Aug 31, 2017

@Chuvisco88, @scottseeker, @Priyesha @Carpediemy, where any of you able to find a solution for this problem ?

@Someone92

This comment has been minimized.

Copy link

Someone92 commented Sep 5, 2017

Property 'get' does not exist on type 'AppConfig'. <-- I get this error when running the following code.
let host: string = this.config.get('host');

This works perfectly
let env: string = this.config.getEnv('env');

If i run
let host: string = this.config['config']['host'])
i can access the config file and the host parameter.

Appreciate all the help.

@sudhakar-sekar

This comment has been minimized.

Copy link

sudhakar-sekar commented Sep 11, 2017

@scottseeker Yes, i am trying to use APP_INITIALIZER to load config file in tests but its not working. How did you manually call config.load in beforeEach().
I am getting an error "Cannot read property load of null".

@ortichon

This comment has been minimized.

Copy link

ortichon commented Sep 12, 2017

@tkir
I've changed your solution so it can work on arrays instead of . separated strings:

Config Service:

  public getConfig(key: any) {
    if (!Array.isArray(key)) {
      return this.config[key];
    }
    let res: any = this.config;
    key.forEach(k => res = res[k]);
    return res;
  }

Component:

this.config.getConfig('host'); // for root item
this.config.getConfig(['parent', 'child']); // for nested

@Someone92
You should use this.config.getConfig('host')

@smitelij

This comment has been minimized.

Copy link

smitelij commented Sep 14, 2017

There is a much simpler way if you're just looking to set global environment properties (host address for example):

http://tattoocoder.com/angular-cli-using-the-environment-option/

@saadiadel

This comment has been minimized.

Copy link

saadiadel commented Sep 19, 2017

when i did as your example, i got this error : Configuration file "env.json" could not be read.
in fact i put this file in same level as .gitignore file in my directory project.
Thank you for your help.

@meshfields

This comment has been minimized.

Copy link

meshfields commented Oct 8, 2017

@smitelij Angular-CLI Environments are before Build, this example are before startup aka runtime! Also your links is horribly outdated, there is official support on GitHub for Build-Time Environments.

@meshfields

This comment has been minimized.

Copy link

meshfields commented Oct 8, 2017

Unhandled Promise rejection: Cannot read property 'host' of null ; Zone: <root> ; Task: Promise.then ; Value: TypeError: Cannot read property 'host' of null
    at AppConfig.webpackJsonp.../../../../../src/app/app.config.ts.AppConfig.getConfig (app.config.ts:19)
@JoseMejia96

This comment has been minimized.

Copy link

JoseMejia96 commented Nov 3, 2017

Yes, i am also trying to use APP_INITIALIZER to load config file in tests but its not working. How did you manually call config.load in beforeEach().
I am getting an error "Cannot read property load of undefined"

I have the same issue with unit test. Did someone find the solution?
@scottseeker How did you call config.load ? could you please give us an example?

@Priyesha

This comment has been minimized.

Copy link

Priyesha commented Nov 24, 2017

I am able to get the external json file in my unit test by mocking the AppConfig. Here is what i have done -
First get the JSON file in your unit test : let mockConfig = require('../../assets/config.json');

Then mock the getConfig function to access the keys and values like this :
class MockAppConfig {
getConfig(key) {
return mockConfig[key];
}
}

You can also mock other functions whichever is required.
@Chuvisco88, @scottseeker, @Carpediemy, @ridj87 I am able to run my unit test with this. You can also try this if it helps anyone.

@okta123

This comment has been minimized.

Copy link

okta123 commented Nov 27, 2017

@Priyesha, could you please elaborate your example with the mockConfig, maybe a complete example, thank you

@xeax

This comment has been minimized.

Copy link

xeax commented Nov 28, 2017

Possible error:
case 'default':
must be:
default:

@deejbee

This comment has been minimized.

Copy link

deejbee commented Dec 1, 2017

In my situation i'm trying to apply the contents of the config file I have loaded to the standard environments/environment import that has already been loaded (with blank fields):

public load() {

    this.http.get('assets/config.json').toPromise().then(allConfigs => {

        //lookup the config using the host
        let specificConfig = allConfigs.json().find((item) => item.host === window.location.hostname);

        if (specificConfig === undefined) {
            //try loading the one marked as isDefault (this should be the production config)
            let defaultProductionConfig = allConfigs.json().find((item) => item.isDefault === true);

            if (defaultProductionConfig !== undefined)
                specificConfig = defaultProductionConfig;
        }
        
        //now override environment, one property at a time.
        //We can't just copy the whole object to ennvironment object since its defaint as Constant
        Object.assign(environment, specificConfig);

    });    
}

The problem is when I try to read the environment.someThing setting from a service more than one service. The first service seems to have the correct value in environment.someThing but when I try to use another service, someThing is blank again.

Anyone else tried this approach of overwriting the standard `envionments/environment' in this way?

@rpeyfuss

This comment has been minimized.

Copy link

rpeyfuss commented Jan 26, 2018

If you are using Angular 5, the AppConfig needs some corrections for it to work:

  1. Http injection in the constructor needs to be changed to HttpClient which is imported from angular/common/http;
  2. change the this.http.get... to this.http.get < any >('env.json')
  3. remove the map function after the http.get call
  4. in the if (request) remove the map function
  5. the json files need to be in the directory /src/app/config
@bhupal4all

This comment has been minimized.

Copy link

bhupal4all commented Feb 16, 2018

I have created the Config service as suggested, Config service is loaded during bootstrap (add Log Config service). But Page is coming as Empty. If remove APP_INITIALIZER line, then the page is loading.

Did anyone face this problem?

I got the solution, Silly me. I forgot to set resolve(true) at the end of the Config Service.

@BBaysinger

This comment has been minimized.

Copy link

BBaysinger commented Mar 16, 2018

I'm getting "Configuration file "env.json" could not be read", but I'm not even seeing an HTTP request for it. Huh?

@kuiperr005

This comment has been minimized.

Copy link

kuiperr005 commented Apr 16, 2018

@anyones and @geekrumper I'm struggling with the same problem of variables being undefined in the constructor of other injectable services. Have you already found a better solution for this?

@m00zi

This comment has been minimized.

Copy link

m00zi commented May 25, 2018

add @Injectable({ providedIn: 'root' }) in the service itself, and no need to add it in app.module.ts

@lucasklaassen

This comment has been minimized.

Copy link

lucasklaassen commented May 31, 2018

While using this alongside HTTP_INTERCEPTORS where in you are using the configuration inside of the HTTP Interceptor it appears that the interceptor loads before this and does not have access to the "environment variables" Has anyone found a workaround for this? @skgyan

@nlern

This comment has been minimized.

Copy link

nlern commented Sep 1, 2018

I have modified the app.config.ts code a bit to make it work in Angular 6 using RxJS 6. I have created an repo for this. To include the json files using ng serve, see angular.json file. For app.config.ts, look inside src/app folder. The service call to AppConfig can be found inside src/app/counter/order-sheet.

@schmorrison

This comment has been minimized.

Copy link

schmorrison commented Sep 20, 2018

I was having an issue in the following example, where the AngularFireModule.initializeApp(config) was being called with an undefined config object. It would seem the imports are being executed before the factory provided to 'APP_INITIALIZER' was being is resolved.

  @NgModule({
    declarations: [
      AppComponent,
    ],
    imports: [
      ...,
      AngularFireModule.initializeApp(firebaseConfig),
    ],
    providers: [
      ConfigService,
      {
        deps: [ConfigService],
        multi: true,
        provide: APP_INITIALIZER,
        useFactory: configProvider,
      },
    ],
    bootstrap: [AppComponent]
  })
  export class AppModule { }

This resulted in AngularFireModule.initializeApp(firebaseConfig) returning an error related to 'not providing projectId'.
What I did instead was add a script in head of the index.html that would be parsed before the Angular files.

<head>
.........

<script type="text/javascript" src="/assets/config/firebase.config.js"></script>

.............
</head>
<body>
  <app-root></app-root>
<script type="text/javascript" src="runtime.js"></script><script type="text/javascript" src="polyfills.js"></script><script type="text/javascript" src="styles.js"></script><script type="text/javascript" src="vendor.js"></script><script type="text/javascript" src="main.js"></script></body>
</html>

And inside the firebase.config.js:

var firebaseConfig = {
    firebase: {
        apiKey: "<API KEY>",
        authDomain: "<AUTH DOMAIN>",
        databaseURL: "<DATABASE URL>",
        projectId: "<PROJECT ID>",
        storageBucket: "<STORAGE BUCKET>",
        messagingSenderId: "<SENDER ID>"
    }
};

Then finally in my NgModule:

declare var firebaseConfig: FirebaseConfig;
@NgModule({
.......
imports: [
........
     AngularfireModule.initializeApp(firebaseConfig),
........
],
.......
})
@pchiarato

This comment has been minimized.

Copy link

pchiarato commented Feb 25, 2019

@skgyan @lucasklaassen I'm getting the same error you were, how did you fix the cyclic dependency issue?

Thanks.

@jeevanandamjobs

This comment has been minimized.

Copy link

jeevanandamjobs commented Apr 1, 2019

This works for me. Modified version, making synchronous HTTP request

export class AppConfig {
static Settings: IAppConfig;
constructor() {

}
load() {
const jsonFile = 'assets/config/config.json';
return new Promise((resolve, reject) => {
var response = this.GetSync(jsonFile);
if (response && response != null) {
AppConfig.Settings = JSON.parse(response);
resolve(true);
}
else
reject(Could not load file '${jsonFile}': ${JSON.stringify(response)});
});
}

public GetSync(url: string): any {
const xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", url, false);
xmlhttp.overrideMimeType('application/json');

xmlhttp.send();
if (xmlhttp.status == 200) {
  return xmlhttp.responseText;
}
else {
  return null;
}

}
}

providers: [
AppConfig,
{
provide: APP_INITIALIZER,
useFactory: initializeApp,
deps: [AppConfig],
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {

}

export class AppComponent implements OnInit {

constructor(

) {

}

async ngOnInit() {

 if (!AppConfig.Settings.AppConfig.production) {
 
}
@kavinda1995

This comment has been minimized.

Copy link

kavinda1995 commented May 16, 2019

@skygan @lucasklaassen @pchiarato and anyone who using using http interceptor.

Follow this way.
https://stackoverflow.com/a/49013534/6686446

The way this is happening is Http Interceptors sit between the HttpClient interface and the HttpBackend. So creating a HttpClient using HttpBackend is bypassing the http interceptor

@dabbid

This comment has been minimized.

Copy link

dabbid commented Aug 16, 2019

@schmorrison, I was stuck on the same problem (I've got several configurable modules), I found an alternative way to load a configuration file before bootstrap:

// src/shared/config/config.model.ts
export interface ExtendedWindow extends Window {
  appConfig: Config;
}

interface Config {
  someProperty: string;
  someOtherProperty: boolean;
  modules: any;
}
// main.ts
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { ExtendedWindow, Config } from './src/shared/config/config.model';

fetch('./config.json')
  .then((response: Response) => response.json())
  .then((config: Config) => {
    (window as ExtendedWindow).appConfig = config;
    platformBrowserDynamic()
      .bootstrapModule(AppModule, {})
      .catch(err => console.log(err));
  });
// src/shared/config/config.provider.ts
import { InjectionToken } from '@angular/core';
import { ExtendedWindow, Config } from './config.model';

export const APP_CONFIG: InjectionToken<Config> = new InjectionToken<Config>('AppConfig');

const appConfigFactory = (): Config => (window as ExtendedWindow).appConfig;

export const appConfigProvider: any = { provide: APP_CONFIG, useFactory: appConfigFactory };

export const configurableModuleFactory = (name: string) => (appConfig: Config): any => appConfig.modules[name];
// src/app.module.ts
import { NgModule } from '@angular/core';
// both configurable modules exposes a configuration injection token
// so I can define a factory provider for these (https://angular.io/guide/dependency-injection-providers#factory-providers)
import { FooConfigurableModule, FOO_CONFIGURABLE_MODULE_CONFIGURATION } from '@scope/foo';
import { BarConfigurableModule, BAR_CONFIGURABLE_MODULE_CONFIGURATION } from '@scope/bar';

import { AppComponent } from './app.component';
import { APP_CONFIG, appConfigProvider, configurableModuleFactory } from './shared/config/config.provider';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    FooConfigurableModule.withConfig(null),
    BarConfigurableModule.withConfig(null),
  ],
  providers: [
    appConfigProvider,
    {
      provide: FOO_CONFIGURABLE_MODULE_CONFIGURATION,
      useFactory: configurableModuleFactory('foo'),
      deps: [APP_CONFIG],
    },
    {
      provide: BAR_CONFIGURABLE_MODULE_CONFIGURATION,
      useFactory: configurableModuleFactory('bar'),
      deps: [APP_CONFIG],
    },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}
@ayyash

This comment has been minimized.

Copy link

ayyash commented Aug 20, 2019

@dabbid oh wow, may be we're over killing it? may be environment rebuild isn't such a bad idea after all :)

@dabbid

This comment has been minimized.

Copy link

dabbid commented Aug 20, 2019

Believe me @ayyash, if I could have done without runtime config, I'd be happy... But it's imposed by my company's context.

@ayyash

This comment has been minimized.

Copy link

ayyash commented Aug 20, 2019

@schmorrison that is a very easy way to do it, unfortunately, it doesn't work on ssr. Unless you rewrite the firebaseConfig in server.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.