Runtime environment configuration for dev/test/prod without rebuilding Angular application

One of the biggest disadvantages of using environment.env.ts is that you have to rebuild your application each time you want to change the configuration and build for each environment separately. But there is a better way where you can download the configuration file before the application runs, which means: one build - many configurations.

August 7, 2022 angularangular-17

Creating configuration files

In the application create a directory called config (or whatever you want) and put configuration files.

Configuration files

Adding configuration files to assets

Add new position to assets in angular.json file. That means, on after building, all configurations will be added to dist directory.

{
  // ...
  "build": {
    "builder": "@angular-devkit/build-angular:browser",
    "options": {
      // ...
      "tsConfig": "tsconfig.app.json",
      "inlineStyleLanguage": "scss",
      "assets": ["src/favicon.ico", "src/assets", "src/config"],
      "styles": ["src/styles.scss"],
      "scripts": []
    }
  }
  // ...
}

Adding src/config to assets means: copy all files to the dist directory. To copy only config.json you should write src/config/config.json.

"assets": [
  "src/favicon.ico",
  "src/assets",
  "src/config/config.json"
],

Creating configuration token

InjectionToken is a special type of token that is used when you need to provide a non-class dependency (for example a interface).

export interface AppConfig {
  apiUrl!: string;
  auth!: {
    type: string;
    token: string;
  };
}

export const APP_CONFIG = new InjectionToken<AppConfig>('App configuration');

The interface should be equal to the JSON schema of the configuration file. If you can, you can try directly import JSON file as a type thanks to TypeScript. More you can read on TypeScript documentation.

Loading configuration before bootstrap application

Fill created service with the downloaded configuration. To do it, open main.ts and add fetch method to provide configuration as the provider.

fetch("config/config.json")
  .then((response) => response.json())
  .then((config) =>
    platformBrowserDynamic([
      {
        provide: APP_CONFIG,
        useValue: config,
      },
    ]).bootstrapModule(AppModule),
  )
  .catch((err) => console.error(err));

As is evident, config.json is downloaded and provided to the whole platform. Thanks to that, you can use ConfigService whenever you want.

If a browser does not support fetch method, you should use XMLHttpRequest.

Example of usage ConfigService

@Component({
  /* ... */
})
export class AppComponent {
  private appConfig = inject(APP_CONFIG);
  token = this.configService.auth.token;
}

Changing configuration file

Before deploying the application you need to replace configuration files. The script should be something like that:

#!/bin/bash
env="prod" # get env from command line
mv dist/app_name/config/config.${env}.json dist/app_name/config/config.json

Do you like the content?

Your support helps me continue my work. Please consider making a donation.

Donations are accepted through PayPal or Stripe. You do not need a account to donate. All major credit cards are accepted.

Leave a comment