A Better Approach to Environment Variables in Angular

Angular provides a default way to configure environment variables, which you can learn more about here. But in a nutshell, you have different environment files, where you store environment variables and during the build process, angular compiler will use the correct environment file to replace and the default environment variables.

So, if you are targeting production environment, angular will replace the default environment file with the production when you use the –prod flag. This works but has some shortcoming. Like for instance to change any of those configurations, you must rebuild and deploy the whole application. Would it be better if you could change some variables, such as API Keys dynamically without need to go through that process?

A Better Approach

The approach is rather simple, and it involves storing the data on the backend on an environment folder. Then during app initial stage or even initialization, you can load the configuration file (JSON) using a service. In this scenario, we will have different JSON files for different environments. We will then let the app load the appropriate configuration based on the environment it has been deployed in.

To distinguish between different environment configurations, we are going to add a new environment name property under each of environment. If we have three environments, then we are going to have 3 environments files: environment.ts, environment.prod.ts and environment.staging.ts for each of our environments.

export const environment = {
  production: true,
  wpEndpoint: 'Some Endpoint',
  environment: 'staging', // environment name property
  ...
};

The same needs to be done for each of our other environment names:

// Production
...
environment: 'staging', // environment name property
...
// Development
...
environment: 'development', // environment name property
...

Now, we can call a http service to retrieve the json files containing  that environments configurations, which we are storing in a directory on a server or AWS S3 bucket.

NB: Any sensitive API Keys should not be stored inside the frontend app. Instead have your backend call those APIs on behalf of the frontend. This ensures they remain private, out of reach from the public.

Loading the Configurations

With the environment name property, you can stitch together the URL to the correct configurations file.

const url = 'some-server-url' + environment.environment + '.json';

And then you can call the API Endpoint as shown below:

this.http.get(url).pipe(
  map((res: Response) => {
    return res.body;
  })
);

NB: You can strongly type cast the results. This allows you to have easy access to the variables inside the JSON File just like a normal environment file. Allowing for intellisense when using  Angular Language Service.

Now anytime you want to change configurations, you simply edit the JSON file containing the configuration of your environment. No need to build and deploy a new version of your application just for simple change.

When to fetch this configuration is up to you. You can load them in the first component, just before anything else. Another option is during app initialization, just before the app has started off. I prefer the last option as it means the configurations will be available to the app as when starts. You can learn more about this here.

Caching

While storing some of the configurations on the backend is a good idea. On the hand, retrieving them each time the app is loaded or reloaded is not a very good idea. You will need to implement some sort of local caching, I suggest using a Service Worker.

A local cache will do two things, first store a local record of the configuration for a specified period – an hour, 30 minutes etc. And secondly speed up your app as it eliminates network latency required while fetching the configurations.

The good thing about using a service worker is you can use performance configuration. This basically means that, it will only check for fresh content from the server after the specified cache period has expired. You can also use freshness settings, in this case it just checks whether there is new content and if not just loads from local cache. You can learn more about Service Workers here.

And all without the need to implement your own caching system, just a few configurations. The only downside is browser support, which you can learn more about here.

Final Thoughts

This method is not perfect, but it provides a much better approach to environment variables. Of course, not every app is going to benefit from this and for a lot of apps out there, the default approach is still a better alternative. It all depends with what your application does, if it relies with a lot of API keys to third party service, then dynamically changing them is not a bad idea. If your environment variables rarely change, and when they do change have to be tested before deployment, then the default approach works just fine.

4 Replies to “A Better Approach to Environment Variables in Angular”

  1. Thanks for your response. Yep, reading your reply the above totally makes sense and is a great solution to that problem. You’re right in that environment sniffing is probably the way to go, just makes me twitchy! It’s literally just different endpoints for different environments – I think i just need to throw out using the environment.ts files for that.

    thanks again

  2. I was really hoping this was going to solve my problem (which is having a Jenkins server run the build process once for each of qa1, qa2, qa3, qa4 and prod) which is incredibly time consuming and slow. As I read it, I would still need to build for each one to tell it which of the config files to read in.

    Have I missed a trick? I’m guessing the alternative would be to environment sniff the url and load based on that, but that seems kinda hacky.

    Could I change my build process to maybe have a global JS var in the index.html which I change post build, which could be used, or would that be considered an even worse alternative?

    Yours looking to reduce builld time by a factor of 4,

    Steven

    1. The goal of this post, was to avoid building and deploying your app each time you change something in the configuration file. But with this method, you have to build separately for each method you are targeting, because naturally its not common – unless i am missing something here – to deploy the same exact code to different environment. I was assuming, one will be an alpha, beta, pre-production and production code, each having to pass a different level of testing before escalating upwards towards production, with frequency of builds decreasing each time.

      If I was in your case where I am deploying the same exact code in different environment, with slightly different environment configuration files, I would use the domain name (or subdomain or IP Address) to determine which applications gets which environment variables. This way, I can build the application once, and once the user opens it up, call a dedicated API Endpoint, which will read my origin(domain name) and serve the correct environment configuration file. The second option is the same but instead of having an API Endpoint, you can have the configuration files hosted in a bucket somewhere else, and read the hostname (domain) in Angular, construct the path to the config file you need and construct a URL and get the configurations. This way, one build can serve in as many environment as you want. Remember, to setup a default configuration file just in case. I hope this was helpful.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.