Angular How-to: Editable Config Files


In this post, Premier Developer consultant Laurie Atkinson walks through how to allow editing of your Angular configuration files after your app has been built, bundled, and deployed.

*This post was updated on 12/3/2018 to reflect the latest changes to Angular.


The Angular-CLI is the recommended way to build a production-ready app, complete with bundling, uglifying, and tree-shaking. An Angular-CLI generated application even comes with a mechanism for creating environment-specific versions. However, those configuration files are in TypeScript and do not allow editing by IT staff or automated deployment tools such as VSTS. This post provides the steps and code samples for using a JSON configuration file, which can be customized for multiple environments.

Define TypeScript interface for config settings

The use of interfaces in an Angular app provides intellisense and type-safety for your entities. For this example, refer to this sample configuration file.

app-config.model.ts

export interface IAppConfig {
    env: {
        name: string;
    };
    appInsights: {
        instrumentationKey: string;
    };
    logging: {
        console: boolean;
        appInsights: boolean;
    };
    aad: {
        requireAuth: boolean;
        tenant: string;
        clientId: string;

    };
    apiServer: {
        metadata: string;
        rules: string;
    };
}

Create JSON config files

A convenient place to store configuration files is under the assets folder of your project. Using the interface defined above, sample files could look as follows:

assets\config\config.dev.json

{
    "env": {
    "name": "DEV"
     },
    "appInsights": {
    "instrumentationKey": "<dev-guid-here>"
     },
    "logging": {
    "console": true,
    "appInsights": false
    },
    "aad": {
    "requireAuth": true,
    "tenant": "<dev-guid-here>",
    "clientId": "<dev-guid-here>"
    },
    "apiServer": {
    "metadata": "https://metadata.demo.com/api/v1.0/",
    "rules": "https://rules.demo.com/api/v1.0/"
    }
}

assets\config\config.deploy.json (Note placeholders that are replaced during deployment)

{
    "env": {
    "name": "#{envName}"
    },
    "appInsights": {
    "instrumentationKey": "#{appInsightsKey}"
    },
    "logging": {
    "console": true,
    "appInsights": true
    },
    "aad": {
    "requireAuth": true,
    "tenant": "#{aadTenant}",
    "clientId": "#{aadClientId}"
    },
    "apiServer": {
    "metadata": "https://#{apiServerPrefix}.demo.com/api/v1.0/",
    "rule": "https://#{apiServerPrefix}.demo.com/api/v1.0/",
    }
}

Continue to use environment.ts with Angular-CLI build

The Angular-CLI creates several TypeScript environment files in the environments folder. They will still be used, but contain only the environment name.

environments\environment.dev.json

export const environment = {
    name: 'dev'
};

environments\environment.deploy.json

export const environment = {
    name: 'deploy'
};

angular.json

"projects": {
  "my-app": {
    "architect": {
      "build": {
        "configurations": {
          "deploy": {
            "fileReplacements": [
              {
                "replace": "src/environments/environment.ts",
                "with": "src/environments/environment.deploy.ts"
              }
            ],
            . . .
          }
        }
      },
      "serve": {
        . . .
        "configurations": {
          "deploy": {
            "browserTarget": "my-app:build:deploy"
          }

Create a service to read config file

This service will read the correct config file and store the result in a static field in this class..

app.config.ts (Note the use of the interface defined above and config file naming convention to retrieve the appropriate file.)

import { Injectable } from '@angular/core’;
import { HttpClient } from '@angular/common/http';
import { environment } from '../environments/environment';
import { IAppConfig } from './models/app-config.model';
@Injectable()
export class AppConfig {
    static settings: IAppConfig;
    constructor(private http: HttpClient) {}
    load() {
        const jsonFile = `assets/config/config.${environment.name}.json`;
        return new Promise<void>((resolve, reject) => {
            this.http.get(jsonFile).toPromise().then((response : IAppConfig) => {
               AppConfig.settings = <IAppConfig>response;
               resolve();
            }).catch((response: any) => {
               reject(`Could not load file '${jsonFile}': ${JSON.stringify(response)}`);
            });
        });
    }
}

Load config file prior to app creation

Angular includes a token named APP_INITIALIZER that allows our app to execute code when the application is initialized. In the app module, use this token to invoke the load method in our config service. Since our method returns a promise, Angular will delay the initialization until the promise is resolved.

app.module.ts

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

export function initializeApp(appConfig: AppConfig) {
  return () => appConfig.load();
}
@NgModule({
    imports: [ , , , ],
    declarations: [ . . . ],
    providers: [
       AppConfig,
       { provide: APP_INITIALIZER,
         useFactory: initializeApp,
         deps: [AppConfig], multi: true }
    ],
    bootstrap: [
      AppComponent
    ]
})
export class AppModule { }

Consume the app settings throughout the application

The config settings are now available from anywhere in the application and they include type-checking provided by the interface.

export class DataService {
    protected apiServer = AppConfig.settings.apiServer;
    . . .
    if (AppConfig.settings.aad.requireAuth) { . . . }
}
export class LoggingService {
    . . .
    instrumentationKey: AppConfig.settings && AppConfig.settings.appInsights ?
                        AppConfig.settings.appInsights.instrumentationKey : ''
    . . .
    if (AppConfig.settings && AppConfig.settings.logging) { . . . }
}

Note: to build a production version of the app using an environment name other than prod, use this command:

ng build --configuration=deploy

Comments (34)

  1. MrO2You says:

    Great post! This exactly addresses a challenge our dev team has been trying to work through. Thanks for putting this together.

    1. Laurie Atkinson says:

      Wonderful to hear that the post helped your team. That’s the goal of this blog. Thanks for the feedback.

  2. Kw says:

    Hi there, any idea to protect the config file from direct access?
    E.G : Prevent access from using path like {domain}\assets\config\config.deploy.json

    1. Laurie Atkinson says:

      I did some research and asked a teammate that specializes in security and identity (thanks, Marius) and unfortunately the answer is ‘no’. Preventing unauthorized access to static files delivered and used in the browser is not possible and that’s why OAuth2 spec calls a browser app a public (as opposed to confidential) client. You could encrypt the file, but the decryption keys must be in the browser. The only way to mitigate is to expose a service that consumes the file on the server and the browser calls the service.

      1. Jagdish Boricha says:

        This is really a good post!

        I think securing AppConfig is not practical. Angular content is anyways unsecured. Its a static content that anyone can downloaded and inspected, including main.js which generally contains content of environment files. Its the API that can be secured. So you hide any secured content behind API and make secure API calls. AppConfig should just contain Url to your API.

  3. Shobhitvaish says:

    Great article!
    One question though – Could you please help me in understanding how placeholders would be replaced during deployment? I am using continuous integration with VSTS and my angular SPA is deployed in Azure web app.

    1. Laurie Atkinson says:

      There is a Replace Tokens build task that you can add to your VSTS build process.

  4. Andrea Re says:

    Great post! This exactly addresses a challenge I’m working on!! Thanks.

  5. Felip says:

    Hi!
    Congratulations for the article, it’s really helpful. However, I’m trying to follow the steps on an Angular 6 application and there are no “environmentSource’ and ‘environments’ keys in the .angular-cli.json file (now named angular.json). Could you please update the tutorial to support Angular 6?

    Thanks a lot,

    1. Laurie Atkinson says:

      For Angular 6, you can specify the environment in the new angular.json file:
      “build”: {
      . . .
      “configurations”: {
      “deploy”: {
      “fileReplacements”: [
      {
      “src”: “src/environments/environment.ts”,
      “replaceWith”: “src/environments/environment.deploy.ts”
      }
      ]
      }
      }

  6. Thibaud Lacan says:

    Thank you for this post, this is exactly what i was looking for!
    Unfortunately i’m getting the following error in my browser console when ng serve:
    Could not load file ‘assets/config/config.dev.json’: {}

    I am using angular 5.2 and followed all the steps.
    Also, in my project i have the config.dev.json and config.deploy.json correctly placed under assets/config/
    Anyone faced the same issue?

  7. Lasse says:

    Hi Laurie

    Thanks for a great article.

    I was wondering if you have any tips on dynamically replacing the configuration file for each deployment when using a dockerized Angular app (e.g. a container with nginx hosting an Angular app)?

    I think a possible solution is to volume mount a configuration file on the host, which is mapped to the corresponding config json file in the apps ‘assets/config/’ folder. Do you see any issues or possible improvements with this approach?

  8. Thibaud Lacan says:

    Great article thanks!
    Be careful not to use HttpClient instead of Http in AppConfig, I was trying to use HttpClient instead and it doesn’t work the same way (probably some lifecycle difference), took me a day to figure it out!

  9. SandyVeliz says:

    Hi! may i ask, my AppSettings.deploy.json dosnt get filled by the aplication settings of the Appservice in Azure, local works great but i cant get the settings from Azure

  10. kjf001 says:

    What if you want to use the config values from the JSON file to provide in a fotRoot() for a module that also needs to be imported in the app.module ?

    I have OAuthModule from ‘angular-oauth2-oidc’ in my app and I want to provide some config in forRoot().

    I need to keep the config values in a JSON file because of deployment to multiple enviroments.
    So I am looking for a way to fetch the JSON config file and then load OAuthModule with those values correctly.

    Do anyone know what proper way to handle this?

  11. Alex says:

    I am using Angular 6 and have same issue with
    Could not load file ‘assets/config/config.dev.json’

    1. Sabi Ridwan says:

      Hi,
      Thanks for the post. i am facing this issues also with angular 6. any solution ?

      Could not load file ‘assets/config/config.dev.json’: {}

      Thanks

    2. Raghavendra says:

      even I am getting could not Could not load file ‘config.DEV.json’ I am using angular 6. how to resolve ?

      1. Serge says:

        You need to make sure you are using the standard “your_application\src\assets” folder (not the one that you could possibly create manually under “your_application\src\app\assets” or anywhere else)

    3. Raul Apaza says:

      Hi Laurie, your post is very usefull, but I’m getting ‘ERROR Could not load file ‘assets/config/config.deploy.json’: {}’ error message. I’m working with Angular 6. Can you help me please.

    4. Raphaël says:

      I just found a solution for Angular 6 with this error.

      Because I use HttpClient from ‘@angular/common/http’ instead of the one of the documentation, I just simplify the http call like that :

      return new Promise((resolve, reject) => {
      this.http.get(jsonFile).toPromise().then(response => {
      AppconfigService.settings = response;
      resolve();
      }).catch((response: any) => {
      reject(`Could not load file ‘${jsonFile}’: ${JSON.stringify(response)}`);
      });
      });

      So I just removed the cast into ‘Response’ interface and the ‘.json()’

      I hope it’ll do the work for you too

      1. I lost time thinking file path had issue due to generic nature of the catch message – Removing it pointed to the exact line..
        I used below code with Observable.
        load() {
        const jsonFile = `/assets/config/config.${environment.name}.json`;
        return this.http.get(jsonFile)
        .subscribe((response : IAppConfig) => {
        debugger;
        console.log(response);
        AppConfig.settings = response;
        });
        }

  12. Eugene says:

    Thank you for the article!
    Can you also show unit test for the code?

    Best regards!

  13. Fabienne says:

    I get Could not load file {} in angular 6…

  14. Martin Smith says:

    I am fairly new to Angular and have come from a .NET background so I was naturally looking for an AppSettings solution. But then when I was try ing to implement this, I found out about the environment files for the first time so I thought ‘what is the point of using an AppSettings file why not just use the environment settings’?. I can’t see a point.

    1. Sune Wøller says:

      @Martin Smith – the angular environment..ts files are build time configuration.
      The solution described here is runtime. This means you can configure the same build differently depending on deployment, CI, etc.

  15. froman says:

    NullInjectorError: No provider for Http!

    1. negast says:

      I resolved following error by importing the http module as well even when I don’t use it. I’m working on angular 6.

      imports: [

      HttpModule,
      ….

  16. Jack O'Mahony says:

    For anyone having the “Could not load file ‘assets/config/config.dev.json’” issue the problem lies with your web.config file.
    I added the below to my web.config file in the section and it started working.

  17. Ritesh Kumar Jaiswal says:

    Hi Pam,

    Thanks for the great article. I have tried to add this solution in Angular 6. Couple of thing i need to add when you are implementing it with Angular 6.

    —————————————————–
    app.module.ts:

    import { HttpModule } from ‘@angular/http’;

    imports: [
    HttpModule,
    ……
    ]
    —————————————————–

    app.config.ts :

    const jsonFile = `assets/config/config.${((environment.production) ? ‘deploy’ : ‘dev’)}.json`;

    —————————————————–

    Thanks.
    Ritesh Kumar Jaiswal

  18. Couple of things.
    Following files meant to be .ts rather than .json
    environments\environment.dev.json
    environments\environment.deploy.json

    Those using Angular 6 and HttpClient may follow these steps
    1- npm install rxjs@6 rxjs-compat@6 –save
    2- In AppConfig service
    import ‘rxjs/add/operator/do’;
    3- Return statement in load() method in AppConfig service can be something like this
    return this.http.get(jsonFile).do((data: IAppConfig) => { AppConfig.settings = data;}).toPromise();

  19. SolDevVB says:

    Had the HTTP issue too in Angular 6.

    In app.module.ts add:
    import { HttpModule } from ‘@angular/http’;


    imports: [

    HttpModule

    This works very well. Fast and clean. Thank you for the post.

  20. AppConfig.settings when used is saying not defined. Settings is static, but still it is undefined when trying to access. please share snippet on how to access the settings.

  21. It would be nice to see this article updated for recent versions of Angular. Http was deprecated way back in Angular v4.3. RxJS has changed in Angular v6. There are comments below on how to modify the code to work with the current Angular release but they include setting RxJS to compatibility mode which isn’t something most developers would want to do.

Skip to main content