Introduction

Business applications nowadays often use a continuous delivery setup with multiple environments. Each environment has its own set of variables like connection strings or links to other systems.

The Angular CLI already provides a concept that allows developers to configure different environments, but it is not designed to fully support continuous delivery.

Missing Pieces

In order to serve an Angular CLI application in a specific environment, you would have to build it specifically for this environment. This means that if you want to support multiple environment like Development, Test or Production in your deployment process, you would have to build the app for all of them.

This contradicts with the continuous delivery principle to build once and deploy many times. Imagine if you need to trigger a new build and deploy just for a variable update.

Loading the Configuration at Runtime

This article describes a very good approach to loading and injecting configuration variables at runtime – Handling Angular environments in continuous delivery.

Why isn’t this enough?

We are currently working on an Angular app for a customer, which is intended to replace a legacy system. The new functionality is going to be added page by page and in the meantime, legacy pages will appear in the new app through iframes.

This means that we need a configuration variable that should hold a link to the legacy system and it should be available during the Angular routing setup. At this phase, the injector and the configuration are not yet loaded.

The Solution

We will inject our configuration file as a script tag in the HTML head. The JavaScript file is added as an asset – assets/configuration.js.

The full source code can be found here – Angular.DynamicConfig.

Pros:

  • The configuration is loaded before Angular starts to load.
  • You don’t have to execute a GET request to fetch the data.
  • You won’t have to pass an Observable and subscribe to it in order to load the configuration.
  • Refactoring existing apps to replace environment with the new configuration is straightforward.
// Create a global window variable that will be
// used to store the configuration.
dynamic_configuration = {
  environment: 'dev',
  legacyUrl: 'https://www.google.com'
}

Here is what the index.html looks like.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>DynamicConfig</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">

  <!-- Here we inject the configuration file. -->
  <script src="assets/configuration.js"></script>
</head>
<body>
  <app-root></app-root>
</body>
</html>

We then export a constant containing the configuration.

export interface IConfiguration {
  environment: string;
  legacyUrl: string;
}

// Extract the IConfiguration interface from the global
// window variable 'dynamic_configuration'.
export const config: IConfiguration = window['dynamic_configuration'];

The exported constant can be used anywhere.

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

import { config } from './../environments/configuration';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  environment = config.environment;
}

Enabling Variable Substitution

To be able to easily swap the configuration.js file and deployment time, we add an additional configuratoin.prod.js, which contains variable placeholders. Most continuous integration systems have tasks that can find and replace placeholders with environment variables.

// Replace all environment variables with placeholders
// that will be populated by the deployment.
dynamic_configuration = {
  environment: '#{AppEnvironment}#',
  legacyUrl: '#{AppLegacyUrl}#'
}

The next step is to instruct the Angular CLI to replace configuration.js with configuration.prod.js when a production build is created (just like environment.ts and environment.prod.ts). This is done in angular.json.

"architect": {
  "build": {
    "configurations": {
      "production": {
        "fileReplacements": [
          {
            "replace": "src/assets/configuration.js",
            "with": "src/assets/configuration.prod.js"
          },
          {
            "replace": "src/environments/environment.ts",
            "with": "src/environments/environment.prod.ts"
          }
        ]
        ...
      }
    }
  }
}

This way, we build the Angular app once in production mode and leave the configuration management up to the continuous delivery tool.

Try it Out


1 Comment

Golan Avraham · December 6, 2019 at 05:30

nice article
i created npm package handling this problem
https://www.npmjs.com/package/@golavr/ng-config

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *