Fetch & load configuration before Angular starts/bootstraps

August 14, 2022  •   3 min read



Problem

We wanted to fetch some configuration which we can't store in environment.ts file. This configuration in our case was some yaml file residing in assets of the same angular app and was being remotely changed using terraform. But the solution can be used to fetch any JSON or any data for that matter. This configuration is important to be loaded before angular starts in case there are some modules in app.module which might need them.

Example -

...
CommonModule,
AuthModule.forRoot({
  domain: 'demo.auth.sudobird.io',
  clientId: '5v9ISakzDIOjB7Y9',
  audience: 'https://sudobird.demo.auth0.sudobird.io',
  httpInterceptor: { allowedList: [{ uriMatcher: () => true }] },
}),
AppRoutingModule,
...

Here we need to put the domain, clientId from remote server before whole app starts.

Solution

Two things:

Generally we don't touch our main.ts file. Let's open it. It must look like this.

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.error(err));

We'll be making changes here. Make another file to store your config say app-config.ts.

This is what it looks like. Taking config from environment and extending it with our remote config that we fetch.

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

let appConfig: any = { ...environment };

export const setConfig = (data: any) => {
  appConfig = { ...appConfig, ...data };
};

export const getConfig = () => {
  return appConfig;
};
Now in the whole app take your config from this file app-config.ts instead of environment.

Here are the steps:

Dynamic import of appModule is important cause otherwise, the modules inside appModule which are initialized with forRoot won't get params from our fetched config.

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { environment } from './environments/environment';
import { setConfig } from './app/common/app-config';

async function initialize() {
  if (environment.production) {
    enableProdMode();
  }

  const appModuleContainer = await import('./app/app.module');
  platformBrowserDynamic()
    .bootstrapModule(appModuleContainer.AppModule)
    .catch((err) => console.error(err));
}

fetch('/assets/configs/global-conf.yaml') // URL to your remote config
  .then((response) => {
    if (response.ok) {
      return response.text(); // make it response.json() if your response is JSON directly.
    }
    throw new Error('Not Found');
  })
  .then(async (response) => {
    const yamlModule = await import('yaml');
    const yaml = yamlModule.default;

    const configObj = yaml.parse(response);
    setConfig(configObj);
  })
  .catch((err) => console.log('error', err))
  .finally(initialize);

I had to transform my yaml config file into JSON. But totally optional for you! Now just import getConfig and use it everywhere in the app.

Note: that I'm calling the initialize function to bootstrap the app in finally block. Cause we want to make sure whatever happens with the config, app should load, and it can pick defaults from environment.

App Module will now become -

import { getConfig } from './common/app-config';

...
CommonModule,
AuthModule.forRoot({
  ...getConfig().auth0,
  httpInterceptor: { allowedList: [{ uriMatcher: () => true }] },
}),
AppRoutingModule,
...


Now get up and do 5 jumping jacks if this blog helped you. I'll get the vibe 😜!