In angular, there is by default a buildtime config, it works well, but it have some disadvantages :

  • To change a config, you have to rebuild your application
  • Doesn't respect the "Build once, deploy everywhere" devops philosophy

For example, if you want to build your angular project into a docker image and use only the buildtime config, you will need to build every time the docker in order to point to another back-end !

The solution is Runtime config.

Runtime config is a config file, usually fetched at startup, containing configs like the server URL or other details.

Luckily, Angular have a hook to run something at startup, APP_INITIALIZER and we can use this to fetch the config at startup !

First, let's make the config.json file, in the assets folder :

{
  "API_URL": "http://localhost:3000"
}

Then we can make the service that will fetch the configs :

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class AppConfigService {
  private appConfig;

  constructor(private http: HttpClient) {}

  loadAppConfig() {
    return this.http
      .get('/assets/config.json')
      .toPromise()
      .then(data => {
        this.appConfig = data;
      });
  }

  getServerUrl(): string {
    return this.appConfig.API_URL;
  }
}

As you can see, you have to return a promise and not an observable, as APP_INITIALIZER needs a promise and not an observable.

Once we have this service, we have to edit the app.module.ts with a function to load the config

const initializerConfigFn = (appConfig: AppConfigService) => {
  return () => {
    return appConfig.loadAppConfig();
  };
};

And provide it in the main module :

@NgModule({
  imports: [
    ...
    HttpClientModule,
    ...
  ],
  providers: [
    ...
    {
      provide: APP_INITIALIZER,
      useFactory: initializerConfigFn,
      multi: true,
      deps: [AppConfigService],
    },
    ...
  ],
})
export class AppModule {}

Here we have it ! Your application will be run after the fetch of the config.json.

Bonus : auto http prefixer

With that, we can make a auto http prefixer using our previously fetch settings :

import { Injectable } from '@angular/core';
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { AppConfigService } from '../services/app-config.service';

 /**
 * Prefixes all requests not starting with `http[s]` with the dynamic config.
 */
@Injectable()
export class ApiPrefixInterceptor implements HttpInterceptor {
  constructor(private readonly appConfig: AppConfigService) {}

   intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (!/^(http|https):/i.test(request.url) && !request.url.startsWith('/assets/')) {
      request = request.clone({ url: this.appConfig.getServerUrl() + request.url });
    }
    return next.handle(request);
  }
}

Then provide it in our AppModule :

@NgModule({
  imports: [
    ...
    HttpClientModule,
    ...
  ],
  providers: [
    ...
    {
      provide: APP_INITIALIZER,
      useFactory: initializerConfigFn,
      multi: true,
      deps: [AppConfigService],
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ApiPrefixInterceptor,
      multi: true
    },
    ...
  ],
})
export class AppModule {}

There you have it ! Automatic url prefixer from a dynamic url fetch from a config.json !