DEV Community

Cover image for Angular dynamic settings
Livio Ribeiro
Livio Ribeiro

Posted on

Angular dynamic settings

(You can find the code shown here in https://github.com/livioribeiro/angular-dynamic-settings-example)

When developing an Angular application, usually your application settings lives in src/environments/environment.ts for development and src/environments/environment.prod.ts for production, and Angular takes care of swapping those during a production build.

But if we have another environment, a staging environment, then we have a problem, since we expect to approve the application running in staging and promote the same code to production, but with Angular's approach to configuration we need to run another build to configure our app to production.

To overcome this problem, I came up a very simple, yet very effective, strategy:

  • Load a javascript file before the application starts that will define a settings object in window.$environment. This is essentially the same as environment.ts.
  • In environment.ts, export the object defined in window.$environment.
  • Tell Angular to add the configuration directory to the build output directory.

First we need to create a directory called src/config and put the javascript file environment.js there:

// src/config/environment.js
window.$environment = {
  production: false,
  api: "dev.my-backend.io",
  // and any other configuration that would go in "environment.ts"
};
Enter fullscreen mode Exit fullscreen mode

And then load the script on index.html:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>MyApp</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <!-- add the following line -->
  <script src="/config/environment.js"></script>
</head>
<body>
  <app-root></app-root>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Now, in environment.ts, export the configuration object:

// src/environments/environment.ts

// this interface is just to making things more typed
interface Environment {
  production: boolean;
  api: string;
}

export const environment = (window as any).$environment as Environment;
Enter fullscreen mode Exit fullscreen mode

And finally, change angular.json build options, adding "src/config" the the assets, and remove the "fileReplacements" completely. I also changed "outputPath" to just "dist":

...
"build": {
  "builder": "@angular-devkit/build-angular:browser",
  "options": {
    // "outputPath" is just "dist"
    "outputPath": "dist",
    "index": "src/index.html",
    "main": "src/main.ts",
    "polyfills": "src/polyfills.ts",
    "tsConfig": "tsconfig.app.json",
    "assets": [
      "src/favicon.ico",
      "src/assets",
      // add the following
      "src/config"
    ],
    "styles": [
      "src/styles.css"
    ],
    "scripts": []
  },
    "configurations": {
      "production": {
        "budgets": [
          {
            "type": "initial",
            "maximumWarning": "500kb",
            "maximumError": "1mb"
          },
          {
            "type": "anyComponentStyle",
            "maximumWarning": "2kb",
            "maximumError": "4kb"
          }
        ],
        // "fileReplacements" is removed
        "outputHashing": "all"
      },
...
Enter fullscreen mode Exit fullscreen mode

We can safely remove src/environments/environment.prod.ts, we don't need it anymore.

Now we can inject a configuration script instead of doing another build.

This approach works great with docker and kubernetes, and we can test it right now!

First, we need a Dockerfile:

FROM node:latest as builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx:latest
COPY --from=builder /app/dist/ /usr/share/nginx/html/
Enter fullscreen mode Exit fullscreen mode

And a configuration to inject (I called it "environment.js"):

// environment.js
window.$environment = {
  production: true,
  api: "prod.my-backend.io",
};
Enter fullscreen mode Exit fullscreen mode

We now build the image and run the container with our new configuration:

docker build -t my-app .
docker run --name my-app \
  -it --rm \
  -p 8080:8080 \
  -v $PWD/environment.js:/usr/share/nginx/html/config/environment.js \
  my-app
Enter fullscreen mode Exit fullscreen mode

With Kubernetes you can use a configmap to store the "environment.js" and mount it as a volume under "/usr/share/nginx/html/config".

And that's it! No more rebuilding angular for staging and production!

Discussion (0)