Implementing Dynamic Environment Configuration in Angular for Avoiding One Build per Environment

Share on facebook
Share on google
Share on twitter
Share on linkedin

One big flaw of the Angular CLI’s way of handling environments, according to the build once, deploy many DevOps principle, is that out-of-the-box Angular CLI wants you to create one separate build per environment.

Why should you only build once and reuse that build in all environments?

The reason why you don’t want to create a separate build for every environment is:

  1. It slows down CI pipelines because it needs to create a build for every environment
  2. It increases the risk of errors/differences in different environments because the builds are separate
  3. It adds unnecessary information about other environments in the code
  4. You might, for security reasons, don’t want to show confidential information in the environment config, which is saved to the version control

All these problems make me wonder why Angular CLI is opting for one build per environment. I sure wanted a solution to these problems.

How are these problems solved?

By making the deployment server substitute a config.json file, which is loaded on runtime by the app.

Making an Angular app use dynamic configurations

We want to make the Angular app only have two environment files: environment.ts and environment.prod.ts.

Why two environments you may ask? One for local dev use, where you can put whatever you want when serving locally and one used when doing a prod build of the app. These environment files are getting most of its values from an app-config.json file loaded on runtime as contrast to having all the environment info hardcoded in the environment files.

Create a new Angular CLI App

The first step is to create a new Angular CLI app by opening the terminal, go to the desired directory for the app and type:

ng new dynamic-config-demo

And this should create a new Angular CLI app for you.

Loading the config.json file on startup

All the environment configuration are gonna be stored in a app-config.json file used by the environment.ts file for referencing these values type-safely.

We simply create an empty JSON file inside the assets folder called app-config.json.

The next step is to load the config.json file using an app.init.ts initialization service:

This service will run on startup and will ensure that the app-configuration is getting fetched by using fetch to get the configuration file and then save it in window for making it globally available to the application. Note, when using fetch you might need polyfills for IE browser support.

The app initializer service is set up in the app.module like this:

Using an app initializer service creates a slight delay on the startup time of the application, but simple stuff like this won’t have a big impact on startup time. Still, I recommend that you let your users know to wait using a loading spinner.

Make the environment files use dynamic values

Now that we are loading the configuration JSON dynamically and saving them in window, we want to be able to reference them type-safely. To do this, we should have three environment files: environment, environment.prod, and dynamic-environment.

environment is for using locally, environment.prod is for production and dynamic-environment is shared with environment and environment.prod for providing dynamic configuration.

dynamic-environment looks like this:

This is used in environment like this:

Now we can try the new dynamic config capabilities out by displaying the environment name like this:

You can find a complete demo of an Angular app with dynamic configuration on my Github here.

Substituting the config.json file on the deployment server

Now that we have set the app up for dynamic configuration the next part is to set up substitution of the config file on the deployment server.

I have done this using Octopus, which has a smart function to substitute a JSON file with configuration values for the environment. Other deployment servers should have similar capabilities to substitute a JSON file right before deploying the app to the front end server.

Conclusion

We discussed the problems with the “Angular CLI” way of handling environment configuration with one build per environment and looked at why it is beneficial to do build once, deploy many instead. We looked at how to implement this using an app.init service which loaded a dynamic configuration file on initialization, with values set by the deployment server.

If you liked this post remember to comment, share and follow me on Twitter.

Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Related Posts and Comments

Error, loading, content…? Use this page pattern for your Angular apps

When developing Angular applications, it’s common for pages to transition through three key states: error, loading, and show content. Every time you fetch data from an API, your page will likely show a loading indicator first, and then either render the content successfully or display an error message if something goes wrong. This pattern is

Read More »

How to do Cypress component testing for Angular apps with MSW

In this post, we will cover how to do Cypress Component testing with MSW (mock service worker) and why it’s beneficial to have a mock environment with MSW. The mock environment My recommendation for most enterprise projects is to have a mocking environment as it serves the following purposes : * The front end can

Read More »

Handling Authentication with Supabase, Analog and tRPC

In this video, I cover how to handle authentication with Supabase, Analog and tRPC. It’s based on my Angular Global Summit talk about the SPARTAN stack you can find on my blog as well. Code snippets Create the auth client Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Read More »

21 thoughts on “Implementing Dynamic Environment Configuration in Angular for Avoiding One Build per Environment”

  1. Pingback: Dynamic Translations in Angular – Christian Lüdemann IT

  2. Lars Gyrup Brink Nielsen

    If other app initializers needs the app config, you’ll want to load it before bootstrap. This is done by fetching it in `main.ts` and providing the config object as a constant to the `extraProviders` array parameter of `platformBrowserDynamic` or `platformBrowser`.

  3. Niklas Speich Hegnelt

    That’s great.
    Any idea on how to solve multiple scss styles as currently they are done with file replacement in the build configs.
    Preferebly without serving all the css on every deployment (like how it would be done using the Material approach)

  4. when I use ng serve is working but when use ng build –configuration=production I get this exception:


    main.20c9ac0bf4f24249d47b.js:1 Uncaught TypeError: Cannot read property 'environment' of undefined
    at e.get [as environment] (main.20c9ac0bf4f24249d47b.js:1)
    at new e (main.20c9ac0bf4f24249d47b.js:1)
    at Module.zUnb (main.20c9ac0bf4f24249d47b.js:1)
    at f (runtime.26209474bfa8dc87a77c.js:1)
    at Object.0 (main.20c9ac0bf4f24249d47b.js:1)
    at f (runtime.26209474bfa8dc87a77c.js:1)
    at t (runtime.26209474bfa8dc87a77c.js:1)
    at Array.r [as push] (runtime.26209474bfa8dc87a77c.js:1)
    at main.20c9ac0bf4f24249d47b.js:1
    get @ main.20c9ac0bf4f24249d47b.js:1
    e @ main.20c9ac0bf4f24249d47b.js:1
    zUnb @ main.20c9ac0bf4f24249d47b.js:1
    f @ runtime.26209474bfa8dc87a77c.js:1
    0 @ main.20c9ac0bf4f24249d47b.js:1
    f @ runtime.26209474bfa8dc87a77c.js:1
    t @ runtime.26209474bfa8dc87a77c.js:1
    r @ runtime.26209474bfa8dc87a77c.js:1
    (anonymous) @ main.20c9ac0bf4f24249d47b.js:1
    content-script.js:1 was injected!

  5. it worked perfectly:


    import { environment } from '../../src/environments/environment';
    import * as ENV_DEV from '../../src/environments/environment.dev';
    import * as ENV_PROD from '../../src/environments/environment.prod';
    import * as ENV_QA from '../../src/environments/environment.qa';

    import { AppModule } from './app/app.module';

    (async () => {
    const response = await fetch('assets/app-config.json');
    const json = await response.json();
    const env = json.environment;
    if (env === 'prod') {
    Object.assign(environment, ENV_PROD.environment);
    } else if (env === 'dev') {
    Object.assign(environment, ENV_DEV.environment);
    } else if (env === 'qa') {
    Object.assign(environment, ENV_QA.environment);
    }
    if (environment.production) {
    enableProdMode();
    }
    platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.error(err));
    })();

  6. Rafael Trindade Chiappetta

    Hi. How could it be achieved into a monorepo app to make this config available among all the apps?

  7. Pingback: Why Angular Teams Fail at Code Sharing and How This Monorepo Approach Will Fix It – Christian Lüdemann

  8. BUILD ONE
    MORE TIME

    #insert pulp fiction ASCII art here but don't really cuz that'd be rude#

    I DOUBLE DARE YOU

    Nah but seriously I can’t remember the last time I subscribed to something, but you actually got me to subscribe with that unexpected meme combined with the popup asking me to subscribe right after that. Congrats, good writing plus a perfectly designed user experience there just made me do something I generally don’t do. Anyway back to reading.

  9. Hey. Good article. Quick question. What about the –prod argument. We specifically do that to production because it does… well production related things.

    Ahead-of-Time (AOT) Compilation: pre-compiles Angular component templates.
    Production mode: deploys the production environment which enables production mode.
    Bundling: concatenates your many application and library files into a few bundles.
    Minification: removes excess whitespace, comments, and optional tokens.
    Uglification: rewrites code to use short, cryptic variable and function names.
    Dead code elimination: removes unreferenced modules and much unused code.

    but, we keep the dev environment free of –prod so it is easier to debug.
    So, we need multiple builds.

  10. Actually this method hits api to fetch configuration which can be viewed from network response of browser. So I think this idea is not to fetch confidential information from api implementing runtime configuration . “You might, for security reasons, don’t want to show confidential information in the environment config, which is saved to the version control” this statement is really joke from your approach. As well this approach is best application for those who assume that there exists no user who can inspect the network response.

  11. Nice Article, It solved my problem.
    But in starting its taking some time to start or redirect the application.
    Is There any idea to reduce the time?
    Thanks

  12. Muneeswari Ganesan

    I am getting this error: dynamic-environment.ts:5 Uncaught TypeError: Cannot read property ‘environment’ of undefined
    at Environment.get [as environment] (dynamic-environment.ts:5) …..
    Any hint to resolve this error?

  13. Pingback: How do I alter Environment .ts Variables or Constants Post Build In Angular 8, host URL and API URL? | MVR

  14. Ravi Kant Shivhare

    Nice article.
    Since config.json is loaded in angular during APP_INITIALIZER.
    If we have loaded config.json from database and now during runtime if we have changed some configuration value in database and also updated config.json then how we can reload updated config.json in angular?

Leave a Comment

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