DEV Community

seanbh
seanbh

Posted on • Originally published at Medium on

How to Get Environment Specific Configuration at Runtime in Angular


Photo by Rima Kruciene on Unsplash

In this post, I’m going to show you one way to retrieve environment specific configuration information dynamically with Angular.

What are the Options for Accessing Environment Specific Config Settings?

You have at least two options for accessing environment specific configuration information in Angular. You can set static values in environment files or you can retrieve values at runtime from a config file or API. Let’s discuss each in turn.

Environment Files

If you know your target environment at build time, you can use environment files to store configuration information. You can define a file for each environment that you need to target, and the values in the appropriate file will be included based on the --configuration flag that is passed to the build command. I really like environment files and they would be my first choice. But in my scenario, I do not know at build time, in what environment the code will be run, because the same build gets deployed to multiple environments. So I need to retrieve the info at runtime.

Config File or API

For the config file approach, you would deploy a JSON file in the assets folder of your application, and then you would need some process outside of Angular to swap out the file based on the environment.

Most of the time, the API host is the config info you need in the first place, so the API option might not be helpful. But in my case, my Angular app is hosted by a legacy app with its own API that I can access with a relative URL, and it has the config info I need (which includes a different API host, which is what I need to get).

Both approaches have the same implementation. Let’s discuss that next.

Accessing App Configuration at Runtime

The InjectionToken

Create the interface for your app configuration in app.config.ts (I like putting it in the root, next to app.module.ts). This represents the shape of your configuration information.

export interface AppConfig {
  apiHost: string;
}
Enter fullscreen mode Exit fullscreen mode

Next, create an InjectionToken with that interface, that will represent your app config object, and allow you to inject it where it’s needed. This can go in app.config.ts as well.

import { InjectionToken } from '@angular/core';

export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
Enter fullscreen mode Exit fullscreen mode

An InjectionToken is a kind of placeholder you can put in code with the expectation that it will be populated at runtime. It’s a basic DI mechanism and is often used to represent interfaces or strings. So you can say, I need this interface or string in this function, but I’m going to decide at runtime what the actual value will be, or let the users of my code decide what it will be. Using different values for testing is also a common use case.

Retrieving the Config Values

Next we need to retrieve the app config values. You’ll most likely want to make sure that you have loaded the config before your application runs. For this, Angular has provided the APP_INITIALIZER InjectionToken.

First, in app.module.ts, above the @NgModule decorator, add the following constant and function:

const MY_APP_CONFIG: AppConfig = {
  apiHost: '',
};
function initializeAppFactory(httpClient: HttpClient): () => Observable<AppConfig> {
  return () =>
    httpClient.get<AppConfig>('/api/config/getconfig').pipe(
      tap((config) =>
        Object.assign(MY_APP_CONFIG, {
          ...config,
          apiHost: `https://${config.apiHost}`,
        })
      )
    );
}
Enter fullscreen mode Exit fullscreen mode

Replace MY_APP_CONFIG with an appropriate name for your app, and replace the URL in the httpClient.get function with the URL to your API or JSON file in the assets folder. The purpose of this function is to retrieve your config values and put them in the MY_APP_CONFIG constant which will be used later.

Now we need to tell Angular to call this function when the app initializes by adding this to the providers array of the NgModule object:

{ 
  provide: APP_INITIALIZER, 
  useFactory: initializeAppFactory, 
  deps: [HttpClient], 
  multi: true 
}
Enter fullscreen mode Exit fullscreen mode

Here we are telling Angular that when it’s looking for APP_INITIALIZER values, it should use our function. The useFactory property (as opposed to useValue or useClass), let’s Angular know to expect a function. We tell Angular that our function is going to need HttpClient, and that there may be other values provided for APP_INITIALIZER elsewhere (multi: true).

Finally, we need to tell Angular to use the actual config info (now stored in MY_APP_CONFIG), whenever APP_CONFIG is wanted, by adding this line to the providers array of the NgModule object:

{ provide: APP_CONFIG, useValue: MY_APP_CONFIG }
Enter fullscreen mode Exit fullscreen mode

Now, in your app, you can use the retrieved config info by injecting the APP_CONFIG token (remember we just told Angular what to use whenever this token is requested):

appConfig = inject(APP_CONFIG);
...
url = this.appConfig.apiHost
...
Enter fullscreen mode Exit fullscreen mode

That’s it! Now you can retrieve app config values dynamically at runtime, and have those values available before any of your application code runs.

Full app.config.ts

import { InjectionToken } from '@angular/core';

export interface AppConfig {
  apiHost: string;
}

export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
Enter fullscreen mode Exit fullscreen mode

Full app.module.ts

import { APP_INITIALIZER, NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HttpClient } from '@angular/common/http';
import { Observable, tap } from 'rxjs';
import { APP_CONFIG, AppConfig } from './app.config';

const MY_APP_CONFIG: AppConfig = {
  apiHost: '',
};
function initializeAppFactory(httpClient: HttpClient): () => Observable<AppConfig> {
  return () =>
    httpClient.get<AppConfig>('/api/config/getconfig').pipe(
      tap((config) =>
        Object.assign(MY_APP_CONFIG, {
          ...config,
          apiHost: `https://${config.apiHost}`,
        })
      )
    );
}

@NgModule({
  declarations: [AppComponent],
  imports: [],
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: initializeAppFactory,
      deps: [HttpClient],
      multi: false,
    },
    { provide: APP_CONFIG, useValue: MY_APP_CONFIG },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Bibliography

Top comments (0)