Multi-Environment Angular With Dynamic Backend URI in Docker Compose

Multi-environmental Angular application with docker-compose

Multi-environmental Angular application with docker-compose


Our goals are simple — one frontend for different backends. Build once, reuse everywhere.

We did it with Angular 8 and docker-compose, and we are deploying our Angular application without rebuilding it for different backends.

Every client will own his own copy of the solution, without a centralized backend service. So our frontend should be able to change his requests endpoint dynamically during the start process.

Our delivery process is:

As we don’t develop a centralized backend, we need our front-end application to be customizable for backend URI. And an important point here is that we decided to develop our front application with Angular 8. This means that after build we will receive static files **html/css/js**, which should include backend URI.

If you worked with Angular applications you know that it may not work out-of-the-box, so we will add our own service, which will manage backend URI.

Standard Web App workflow

WebApp workflow

WebApp workflow


Web App Workflow

0: A user connects to a browser

1: The user inputs URL and the browser asks Server for files;

2: The user browser receives static files html/css/js which are the Angular web app.

3: Those steps represent user interaction with web app, which will communicate with the backend server and build representation on the client-side with JS code.

So by default, when we deploy our web app to the server it has to have backend service URI hardcoded in files, which is done automatically with Angular CLI during the build process.

You can find more about Angular in the official documentation.

Multi Backend Angular Web App

Our implementation differs from the original, with an additional file config.json which is in the assets directory and is served with other Angular files.

Content of the file:


Java
 




x


1
// config.json example
2
{
3
    "url": "https://my_backend.uri"
4
}


 Here is our requests stack trace for the Angular web app:

Stacktrace of WebApp

From step 1-8, we are downloading files needed for Angular. And on step 9 we are receiving our config.json, which contains the URL of our backend service, to which the web app will send all requests.

Stacktrace of WebApp


Implementation Angular

I have created file config.teml.json which contains next template,

JSON
 




x


 
1
{
2
    "url": "https://${SERVER_NAME}:${PORT_BACK_SSL}" 
3
}


where SERVER_NAME is the backend service IP or domain name, and PORT_BACK_SSL is the backend service port for SSL communication if needed.

 Then we should create a service which will configure our variables and inject them into the Angular app:

Java
 




x
30


1
// app-config.ts
2
import { Injectable } from '@angular/core';
3
import { HttpClient } from '@angular/common/http';
4
interface Config {
5
  url: string;
6
}
7
export interface IAppConfig {
8
  baseUrl: string;
9
  baseDMUrl: string;
10
  baseStandardUrl: string;
11
  load: () => Promise<void>;
12
}
13
@Injectable()
14
export class AppConfig implements IAppConfig {
15
  public baseUrl: string;
16
  public baseDMUrl: string;
17
  public baseStandardUrl: string;
18
constructor(private readonly http: HttpClient) {}
19
public load(): Promise<void> {
20
    return this.http
21
      .get<Config>('assets/config.json')
22
      .toPromise()
23
      .then(config => {
24
        this.baseUrl = config.url;
25
      });
26
  }
27
}
28
export function initConfig(config: AppConfig): () => Promise<void> {
29
  return () => config.load();
30
}


   

After, of course, we should inject this service into our app.module.ts:

Java
 




xxxxxxxxxx
1
27


 
1
import { AppConfig, initConfig } from './app-config';
2
import { NgModule, Inject, APP_INITIALIZER } from '@angular/core';
3
import { HttpClientModule } from '@angular/common/http';
4
import { BrowserModule } from '@angular/platform-browser';
5
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
6
import { AppComponent } from './app.component';
7
@NgModule({
8
  imports: [
9
    BrowserModule,
10
    BrowserAnimationsModule,
11
    HttpClientModule,
12
  ],
13
  providers: [
14
    AppConfig,
15
    {
16
      provide: APP_INITIALIZER,
17
      useFactory: initConfig,
18
      deps: [AppConfig],
19
      multi: true
20
    },
21
  ],
22
  declarations: [AppComponent],
23
 
24
  bootstrap: [AppComponent]
25
})
26
export class AppModule {
27
}


As you can see we are using the APP_INITIALIZER  provider to initialize our AppConfig module.

Docker

The Docker image for our Angular application is based on Nginx:

Java
 




x


 
1
# base image
2
FROM nginx:stable-alpine
3
# clean NGINX static files
4
RUN rm -rf /usr/share/nginx/html/*
5
# copy WebApp built files into NGINX directory
6
COPY ./dist/app /usr/share/nginx/html


Docker Compose

Our Docker Compose file is key for multi-backend deployment with an Angular app:

Java
 




x
17


1
version: "3"
2
services:
3
  backend:
4
    image: privateregistry.azurecr.io/cc/backend:develop
5
    expose:
6
      - "8080"
7
frontend:
8
    image: privateregistry.azurecr.io/cc/frontend:develop
9
    ports:
10
      - "80:80"
11
      - "443:443"
12
      - "8080:8080"
13
    links:
14
      - backend:backend
15
    env_file:
16
      - ./backend.env
17
    command: /bin/sh -c "envsubst '$${SERVER_NAME},$${PORT_BACK_SSL}' < /usr/share/nginx/html/assets/config.templ.json > /usr/share/nginx/html/assets/config.json && exec nginx -g 'daemon off;'"


And the key component here is the last line:


Shell
 




x


 
1
command: /bin/sh -c "envsubst '$${SERVER_NAME},$${PORT_BACK_SSL}' < /usr/share/nginx/html/assets/config.templ.json > /usr/share/nginx/html/assets/config.json && exec nginx -g 'daemon off;'"


   

Where we execute swapping of strings  ${SERVER_NAME}  and  ${PORT_BACK_SSL}  in config.templ.json and we store this file in place of  config.json which will be used by the frontend.

 Values of those variables are taken from the environment variables for the Docker Compose environment, which are initialized in the file backend.env    

Java
 




x


 
1
SERVER_NAME=mydomainfortest.westeurope.cloudapp.azure.com
2
PORT_BACK_SSL=443


Automation

This moment is essential because creating an undefined number of files that contain separate  SERVER_NAME instances for each client will be overcomplicated.

So I use this script, which is executed before each pull of images:


Java
 




x


 
1
export SERVER_NAME=$(curl https://ipinfo.io/ip) && \
2
echo -e "\nSERVER_NAME="$SERVER_NAME >> backend.env


Thanks for reading!

If you have other errors during deployment or you interested in another topic, please add comments. I'm interested in the dialog.

Further Reading

Deployment Automation for Multi-Platform Environments

Dockerizing an Angular App via Nginx [Snippet]

 

 

 

 

Top