How do I externalize property values for an Angular 7 application?

7/17/2019

I have an Angular 7 application where the app.module.ts files looks like the following. Note that in this example, there are 2 modules with each having 1 key whose value needs to be externalized. By externalized, I mean the values should be acquired from the environment at runtime.

@NgModule({
 declarations: [ ... ],
 imports: [
  SomeModule.forRoot({ apiKey1: "needs to be externalized" }),
  AnotherModule.forRoot({ apiKey2: "needs to also be externalized" })
 ],
 providers: [ ... ],
 bootstrap: [AppComponent]
})
export class AppModule { }

What I do is build this application (e.g. ng build and then containerize it using Docker). At deployment time, the DevOps person wants to run the docker container as follows.

docker run -e API_KEY_1='somekey' -e API_KEY_2='anotherkey' -p 80:80 my-container:production

Note that API_KEY_1 should map to apiKey1 and API_KEY_2 should map to apiKey2.

Is there any disciplined way of externalizing values for an Angular application?

I thought about writing a helper script to do string substitution against the file, but I think this approach is not very disciplined (as the transpiled Angular app files are obfuscated and minified). The script would run at container startup, read the environment variables (key and value), and then look at the files to replace the old values with the ones from the environment.

Eventually, the Angular app will be orchestrated with Kubernetes. I'm wondering if there's anything there that might help or influence how to externalize the values in a best practice way.

Any help is appreciated.

-- Jane Wayne
angular
docker
kubernetes
properties-file

1 Answer

7/18/2019

You could use a substitution in custom entry-point.

FROM nginx

RUN apt-get update && apt-get -y install gettext-base nginx-extras

ADD docker-entrypoint.sh /
ADD settings.json.template /

COPY dist /usr/share/nginx/html

ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

With a docker-entrypoint.sh like this:

#!/bin/bash

envsubst < "settings.json.template" > "settings.json"
cp settings.json /usr/share/nginx/html/assets/

# Launch nginx
exec "$@"

And a settings.json.template:

{
  "apiKey2": "$API_KEY_2",
  "apiKey1": "$API_KEY_1"
}

Then on your source you add a file settings-loader.ts

export const settingsLoader = new Promise<any>((resolve, reject) => {
  const xmlhttp = new XMLHttpRequest();
  const method = 'GET';
  const url = './assets/settings.json';

  xmlhttp.open(method, url, true);

  xmlhttp.onload = function() {
    if (xmlhttp.status === 200) {
      const _environment = JSON.parse(xmlhttp.responseText);
      resolve(_environment);
    } else {
      resolve();
    }
  };

  xmlhttp.send();
});

And on your main.ts:

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

import {AppModule} from './app/app.module';
import {environment} from './environments/environment';
import {settingsLoader} from 'settings-loader';

settingsLoader.then((settings) => {
  if (settings != null) {
    environment.settings = Object.assign(environment.settings, settings);
  }

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

  platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.log(err));
});

Then you should have access in your code with

import {environment} from '../environments/environment';

console.log(environment.settings.apiKey1);
-- XciD
Source: StackOverflow