How to configure Angular for staging environments in the cloud

You can find many tutorials about how to configure angular for different staging environments but what do you do when you have to deploy one existing app on different stages without building the app for every stage? In this tutorial I will show you one solution I used to solve this problem.

To understand the problem in more detail we will have a look at the deployment. We regard a typicall system with a deployment pipeline for all stages.

  • The first step build the app.
  • Secondly a docker container is beeing build.
  • At last this container is delivered to different stages.

So we have one builded app and have to configure it from outside.

1. Create and load configuration

We are creating a new file config.json in the directory “src/app/assets/”

# Example of src/app/assets/config.json
{
  "environment": {
    "name": "$ENV"
  }

}

You can insert any valid json in this file. For values which should be replaced from outside later you have to start with the “$” e.g. $VALUE_FROM_OUTSIDE.

2. Load configuration inside Angular

On the next step we have to load the configuration inside the Angular app. This can be done by adding the following code to the main.js.

As you can see, if a local config file is found it will also be loaded. We need this file to keep our local development working because we don’t have a staging environment if we work on our local machine.
Of course we replace the variables with actual values for development in the local file.

Important: add config-local.json to the .gitignore file

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import {APP_CONFIG, ConfigSettings} from "./app/config";

fetch('/assets/config.json')
.then(config => config.json())
.then((settings: ConfigSettings) => {
  if (settings.environment.name === '$ENV') {
    return fetch('/assets/config-local.json')
    .then(config => config.json())
    .then((settings: ConfigSettings) => { return settings})
  }
  return settings;
})
.then((settings: ConfigSettings) => {

      if (environment.production) {
        enableProdMode();
      }

      platformBrowserDynamic( [{ provide: APP_CONFIG, useValue: settings}])
      .bootstrapModule(AppModule)
      .catch(err => console.error(err));
    }
);

Also we need a model for holding the data and providing a service for accessing them. This will be done i an file called config.ts

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

export const APP_CONFIG = new InjectionToken<ConfigSettings>('settings from config.json');

export class ConfigSettings {

  environment!: {
    name: string;
  };

}

3. Summary

We now have two config files one for the staging and one for the local development. We also load these files at the start of angular.

Next we want top use the configured values inside our app and secondly we want to replace the staging values with the right values from environment.

4. Using the config-service

To get access to our config values we just have to inject the config service in the constructor. See following example:

import {Inject, Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {Observable} from "rxjs";
import {APP_CONFIG, ConfigSettings} from "../config";

@Injectable({
  providedIn: 'root'
})
export class TestService {

  constructor(@Inject(APP_CONFIG) private settings: ConfigSettings) {
    console.log('Environment', this.settings.environment.name);
  }


}

5. Staging the app

Usually, if staging is used, the app is getting deployed inside a docker file. If the docker file itself is started directly or inside a cloud does not matter. In this tutorial we will use nginx to deliver our app.

To mention here is that we set the variable CONFIGFILE and copy a start script to the right directory. At the end this copied script will be started. All the magic is done inside the new start script.

FROM docker.repository.muc.lv1871.de/nginxinc/nginx-unprivileged:1.18
#!/bin/sh

## Remove default nginx index page
USER root
RUN rm -rf /usr/share/nginx/html/*
USER nginx
# Copy from the stage1
COPY dist/app /usr/share/nginx/html
ENV CONFIGFILE=/usr/share/nginx/html/assets/config.json
COPY ./start-nginx.sh /usr/bin/start-nginx.sh
# Copy config file
COPY nginx.conf /etc/nginx/nginx.conf

USER root
RUN chmod +x /usr/bin/start-nginx.sh
USER nginx

EXPOSE 8080

ENTRYPOINT ["start-nginx.sh"]

And finally the new start script start-nginx.sh where we replaces the variables inside the config file with these from the environment. So we have one app, one dockerfile and one config-file but with different values depending on the environment variables.

#!/bin/bash
export EXISTING_VARS=$(printenv | awk -F= '{print $1}' | sed 's/^/\$/g' | paste -sd,);
cat $CONFIGFILE | envsubst $EXISTING_VARS | tee $CONFIGFILE
nginx -g 'daemon off;'

6. The end

In this article I showed you how to implement configuration over multiple stages inside an Angular app. If you have questions or hint or anything else feel free to comment or contact me.