How to use frontend environment variables without rebuilding the Angular app

A DevOps engineer needed to pass environment variables to the container with the frontend to apply without rebuilding the container again. Angular does not provide any out-of-the-box solution for this purpose.

Our first approach was simple: create a JSON template app.config.template.json in the project assets folder, which looked like this:

				
					{
  "var1": "$VAR",
  "varObj": {
    "varA": "$VARA",
    "varBool": "$VAR_B",
  }
}
				
			

Then generate a JSON file from this template by replacing the environment variables with the envsubst command-line utility. This utility is included in the nginx:alpine image.

				
					"envsubst < ./assets/app.config.template.json > ./assets/app.config.json
				
			
In the Dockerfile, the command looked like this:
				
					CMD ["/bin/sh",  "-c",  "envsubst < /usr/share/nginx/html/assets/app.config.template.json > /usr/share/nginx/html/assets/app.config.json && exec nginx -g 'daemon off;'"]
				
			

There were two problems with this approach:

  1. Boolean values needed to be converted from strings to values every time.
  2. It was necessary to check for empty strings if an environment variable was not specified.

All this had to be done manually for each variable in the app.config.json loading service. This is why it was not desirable to leave the first approach for a long time and it was decided to solve these problems in it. Plus, it was necessary to support the naming of nested variables, just like .NetCore does.

Therefore, a Node.js script was written that would create a JSON file from the JSON template, skipping empty fields, and converting numbers or boolean values where necessary. The node-config package in Node.js supports a similar format for environment variables, where you can specify not only the variable name but also the variable format. We take this idea, too.

				
					{
  "var1": "VAR",
  "varObj": {
    "varA": "VARA",
    "varBool": {
      "__name": "VAR_B",
      "__format": "boolean",
    }
  }
}
				
			

The script was written. We support this format of config file.

				
					{
  "var1": "VAR",
  "varObj": {
    "varA": "",
    "varBool": {
      "__name": "VAR_B",
      "__format": "boolean",
    }
  },
}
				
			

Based on this configuration, the script will try to find variables with the names VAR, varObj__varA, varObj__VAR_B in the environment. And for varObj__VAR_B, it will convert the string to a boolean value. If these values are not found, it will skip them.

For example, if the environment variables are set like this:

				
					varObj__varA=VAR-A
varObj__VAR_B=true
				
			

then the resulting JSON will be like this:

				
					{
  "varObj": {
    "varA": "VAR-A",
    "varBool": true
  },
}
				
			
We did not want to pull Node inside the Docker runtime, so we turned the script into an executable file using vercel/pkg. This executable will be run when the container starts up:
				
					CMD ["/bin/sh",  "-c",  "env-config /usr/share/nginx/html /usr/share/nginx/html/assets && exec nginx -g 'daemon off;'"]
				
			

This allows us to restart the container with new variables without rebuilding it, as before.

In our project, we use environment.ts a lot for application configuration, so the final configuration is a merge of the variables specified in JSON and the variables specified in environment.ts.

Facebook
Twitter
LinkedIn
Picture of Mikhail Bondarenko
Mikhail Bondarenko
All Posts