DEV Community

Cover image for Compile-time vs. Runtime configuration of your Angular App
Juri Strumpflohner for Angular

Posted on • Originally published at juristr.com

Compile-time vs. Runtime configuration of your Angular App

When you develop a bigger application, chances are quite high that you need some kind of configuration. That can range from simply visualizing the app's version number to injecting custom themes etc. In Angular you have different kind of approaches: compile-time and runtime configurations. Let's take a look at both of them.

Compile-time configuration

(Click to open Egghead lesson)

What is compile-time configuration? It basically means that you compile your configuration into your app, at the time when you compile and bundle it. If you're using the Angular CLI there's already a preconfigured setup for having such compile-time configuration options.

Inside the environments folder you have a environment.ts and environment.prod.ts file.

// environment.ts
export const environment = {
  production: false
};
Enter fullscreen mode Exit fullscreen mode
// environment.prod.ts
export const environment = {
  production: true
};
Enter fullscreen mode Exit fullscreen mode

Since these are just plain JavaScript objects, you can add your environment specific properties on them.

The default main.ts file that is responsible for bootstrapping our Angular application uses those environment files to determine whether the app is running production mode, in order to apply some runtime optimizations such as calling enableProdMode().

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

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

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.log(err));
Enter fullscreen mode Exit fullscreen mode

You can arbitrarily get a reference to that environment object by simply importing environment.ts into your file.

import { environment } from '../environment/environment';

// do something meaningful with `environment`
console.log(environment);
Enter fullscreen mode Exit fullscreen mode

Note, we're always importing environment.ts and never an environment specific file such as environment.prod.ts. The reason is that at compile time, the Angular CLI will take care of renaming the environment specific configuration file into environment.ts and to compile it into your app accordingly.

You can also create new files, say for your "staging" environment. Just create a new environment.staging.ts and make sure to configure it in your angular.json file:

{
  // ...
  "projects": {
    "demo": {
      //...
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          // ...
          "configurations": {
            "staging": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.staging.ts"
                }
              ],
              //...
            },
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              //...
            }
          }
        },
        //...
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Finally, we need to tell the CLI which environment we're building for, s.t. it is able to pick up the proper configuration file. That happens in the scripts section of our package.json:

{
  ...
  "scripts": {
    "ng": "ng",
    "build": "ng build --prod",
    "build:staging": "ng build --prod --env=staging"
  },
  ...
}
Enter fullscreen mode Exit fullscreen mode

Note, we're passing in the --env=<your-environment> flag. the --prod is a built-in command which automatically uses the production configuration already, if not specified otherwise. Moreover it also enables the AOT compilation.

Runtime configuration

(Click to open Egghead lesson)

If however you need to be able to change your app's configuration settings or maybe you even expose them via an API, then you need to use a runtime configuration approach. Normally you simply have some JSON file - say appConfig.json that contains the necessary configuration options which you then deploy with your app on your server. When your app runs, you execute an HTTP request to that JSON file and read the settings.

What you want however, is to start your app only after your settings have been loaded and applied. A naive approach could be as follows. In your app.component.ts you simply add an *ngIf guard:

@Component({
    selector: 'app-root',
    template: `
        <div *ngIf="configLoaded">

        </div>
    `
})
export class AppComponent implements OnInit {
    configLoaded = false;

    ngOnInit() {
        this.http.get('./assets/appConfig.json')
            .subscribe(config => {
                // do something with your configuration
                ...

                this.configLoaded = true;
            });
    }
}
Enter fullscreen mode Exit fullscreen mode

That way your other components wouldn't start unless the configLoaded is true and consequently the <div> is being shown.

While this would work, there's a more elegant way of doing things: we can hook into Angular's bootstrapping phase by using the APP_INITIALIZER token. We first create an Angular service that handles the fetching of our remote configuration...

Read more ยป

Top comments (11)

Collapse
 
blazzze profile image
Blazzze • Edited

this seems like it would work fine (quick&dirty) , but it seems like a way around using the app initializer, which works great btw :)

a good article about it:
davembush.github.io/where-to-store...

Collapse
 
juristr profile image
Juri Strumpflohner

Hi, actually my post describes just that. Using the static compile time approach and the dynamic runtime approach which uses the app_initializer ๐Ÿ˜‰

Collapse
 
blazzze profile image
Blazzze

oh my, totally missed the Read more lol :)

Thread Thread
 
juristr profile image
Juri Strumpflohner

๐Ÿ™ˆ ๐Ÿ˜… haha sorry for that.

Collapse
 
zacharymacke profile image
Zach Macke

I just have to ask what your color-scheme plugin you're using in your vscode first image?

Collapse
 
juristr profile image
Juri Strumpflohner

Oh ๐Ÿ˜… it's an old screenshot so I'm not 100% sure, but as far as I remember it should be the material theme I guess.

Nowadays I mostly switch between Night Owl & Night Owl Light + FiraCode as the font ๐Ÿ™‚

Collapse
 
jonyeezs profile image
Jonathan Yee

When would you have (or what type of) work in the constructor vs app_initializer?

Collapse
 
juristr profile image
Juri Strumpflohner

Well the APP_INITIALIZER starts before the app boots. If you place work in the constructor of say your app component, it'd be very similar. But I find it more clean in the app_initi... as it's meant for this type of work ๐Ÿ™‚

Collapse
 
jonyeezs profile image
Jonathan Yee

Yeah I agree. Any work should be done via app_init. I find it weird to have any code in a module's ctor. I struggled to find an argument not to do work in the ctor

Collapse
 
aleksandar874 profile image
aleksandar87

How would you do if every feature had a switch for dev and prod?

Collapse
 
juristr profile image
Juri Strumpflohner

That's more of a use case for feature toggles (there are a couple of libs out there). But in the end, the feature toggle config could be downloaded via this runtime config I describe in the post & then processed.
The various configs for dev, prod etc...is something the server needs to give you at disposal.
Either via a static json deployed by the CI, or via an API etc..