OS environment variable with Angular 7 and custom webpack plugin

For a long time, server-side development enjoyed a rich set of Toolkits for development, build, and deployment. One of the most convenient features is the ability to externalize configuration from the actual source code. API URL, secrets, threshold limits, etc. can be configured outside the actual code and exposed via the Operating system's environment variables. This promotes a good practice of immutable build packages. These builds can be promoted without a need to make modification in the actual code to adapt to a changing environment.
We have been witnessing an era wherein the magical power of build tools is no more restricted to Server side development. Tools like Grunt, Yeoman, Gulp, Webpack, Browserify etc. have redefined how today's modern User Interfaces are actually built.
I had a problem
I was working on an Angular 6 Project which interacts with Server side API and it is also integrated with an IAM (Identity Access Management) solution. Naturally it requires certain values which differ from environment to environment. I didn't realize a problem as long as I was developing and testing my application locally. But soon, when I started promoting it on Test servers, I hit the first roadblock - configuration.
With Angular 6, I used the "environments" feature. It looks sufficient on the surface. You can create a dedicated configuration file for each environment and then use a command like below to build a project matching to that environment.
ng build --configuration=production
Something is not right
I was not quite comfortable having each environment configuration buried with Code. My git repository was aware of how many configurations I do have. Sometimes we do create an environment for temporary reasons and want to quickly destroy it once those reasons are fulfilled. Creating a configuration for such an environment and adding it to repository was overkill.
With my polyglot experience, I realized that Server-side development has solved this problem with the help of environment variables. For e.g. refer to the following property file configuration in Spring
payment.gateway.url=${PAYMENT_API_URL:https://www.dummypay.com}
The syntax is self-explanatory. Look for an environment variable - PAYMENT_API_URL and assign its value to the property. If the variable is not found, initialize the property with a default value.
If not similar can it be better?
I really enjoy coding APIs because of the strong tooling system. The ease that it provides to simplify complex-looking things and the power to port an application from one environment to another without much hassle. The usage of environmental variables is very appealing and provides numerous benefits. I needed something similar in Angular build. Instead of hardcoding values inside environment.XXX.ts files, I needed an approach to keep one file for development and another with placeholders.
As they say,
That's a bit of an exaggeration. :P There was no need to invent something, but to find the right set of build plug-ins to achieve desired results. I came across a few articles and posts which eventually led me to a webpack plugin - @angular-builders/custom-webpack
Eureka!!!
While I am still learning webpack and Angular 7, I realized that webpack has pretty strong features. The @angular-builders plug-in is definitely an answer to my problem. The plug-in enables an option to configure the webpack plug-in and performs all the default build operations of Angular build. With the help of webpack, accessing environment variables requires a few lines of code. Let me explain the steps one by one.
Configure Dependency
To see the plug-in in action, create an Angular application using Angular CLI. The command line syntax looks like this below
ng new ng-environment
Once all the skeleton project is configured and all the dependencies are downloaded. Make sure to test the application by executing "ng serve" command. This is required to ensure there are no bugs in the existing build and that everything works as expected.
Once you are assured of the Angular Application is working as expected, go to the command prompt or Terminal window in Visual Studio code. Make sure that you are at the root location of your project (Inside a folder where package.json resides). Execute the following command to download the new module dependency
npm install --save-dev @angular-builders/custom-webpack
Note the --save-dev argument is essential. The new module is only required to build a project and not for runtime. This also ensures that there is no overhead on the Angular application runtime.
Webpack plug-in configuration
This step is broken into two parts - Modifying angular.json and definition of custom webpack configuration.
Open angular.json file located at the root of the Angular project. Locate the build section (available inside "projects" > "ng-environment" (project name) > "architect" > "build"
Change the existing builder plugin to
@angular-builders/custom-webpack:browser
Also under the options section, add the following configuration
"options": {
"customWebpackConfig": {
"path":"custom-webpack-config.js"
}
...
}
Schema definition for Environment Variables
Since we are using Typescript to write Angular code, we must declare a Schema to define the list of environment variables. To proceed with the definition, create typing.d.ts file under the src folder. The content of the file should look like the following.
declare var $ENV: ENV;
interface ENV {
API_URL: string;
}
Webpack Plug-in
In the earlier step, we provided a reference of custom-webpack-config.js. The file will have logic to access the environment variables. Have a look at the following code
const webpack = require(\'webpack\');
module.exports = {
plugins: [
new webpack.DefinePlugin({
$ENV: {
API_URL : JSON.stringify(process.env.API_URL)
}
})
]
};
The important thing to note here is that
- The $ENV key matches the variable defined in typing.d.ts
- The name of the API_URL matches the attribute defined in ENV schema
- Environment variable is accessed using NodeJS API - process.env
Production environment - environment.prod.ts
This is the final step. The environment variables can be referenced in our configuration file. I will recommend leaving environment.ts file with the development configuration and modifying environment.prod.ts. The modified file should look like below
export const environment = {
production: true,
API_URL: $ENV.API_URL
};
Congratulation!!! you have made your application portable across environments. No more fiddling with environment.XXX.ts files. Just define OS environment variables and build your application with
ng build --configuration=production