简体   繁体   中英

Runtime Configuration for Angular 6+ Applications

What is the recommended best practice for loading environment specific configuration during runtime of an Angular application? The Angular documentation mentions the use of APP_INITIALIZER, but that is still not early enough in the load process for things such as runtime configuration of imported modules that make use of the .forRoot() convention.

In my use case, I have an authentication service built and imported via a Core module, which is imported by the App module. The authentication library I am using (the angular-oauth2-oidc library) allows for configuration of the automatic appending of access tokens during when importing the module (see this segment ). Since there are constraints in the build environment I am working with that only allows me to produce one common build package to deploy to all environments, I am unable to dynamically set values by using different environment.ts files.

One initial idea is to use the fetch API on the index.html page to load a JSON file containing the configuration onto a global variable, but since the call is asynchronous, there is a chance the configuration will not be fully loaded when the import of the Core module occurs.

This was part of my config setup to bring my app through the build pipeline and took me days. I ended up in a solution using the APP_INITIALIZER loading a REST service and build a AppConfigService for my App. I am using the same angular-oauth2-oidc library.

My Solution for this issue was not to setup the OAuthModule in its forRoot() method. It is called before any configs via APP_INITIALIZER are available - this results in undefined values when applied to the config object given to the forRoot() Method.

But we need a token in the http header. So I used a http interceptor for the attaching of the token like described here . The trick is to setup the OAuthModuleConfig in the factory. Obviously this is called after the app is initialized.

Configure Module

@NgModule({
  imports: [
    // no config here
    OAuthModule.forRoot(),
  ],
  providers: [
   {
    provide: HTTP_INTERCEPTORS,
    useFactory: authenticationInterceptorFactory,
    deps: [OAuthStorage, AuthenticationErrorHandler, OAuthModuleConfig],
    multi: true
   }
 ]
})

Factory for interceptor

const authenticationInterceptorFactory = (oAuthService: OAuthStorage, authenticationErrorHandler: AuthenticationErrorHandler, oAuthModuleConfig: OAuthModuleConfig) => {
const config = {
 resourceServer: {
  allowedUrls: [
   // Include config settings here instead
   AppConfigService.settings.apiURL,
   AppConfigService.settings.anotherApiURL,
  ]
  sendAccessToken: true
 },
}
return new AuthenticationInterceptor(oAuthService, authenticationErrorHandler, config);
};

I have created a library angular-runtime-config for runtime configuration loading for Angular.

Simple usage example

Your custom Configuration class:

export class Configuration {
  readonly apiUrl!: string; // only example
  readonly apiKey?: string; // only example
  // add some other configuration parameters
}

Registering angular-runtime-config module with declaring which configuration files to load. For example, you can determine it by application URL or you can even use Angular injector in the factory or make the factory asynchronous.

import { AngularRuntimeConfigModule } from 'angular-runtime-config';

@NgModule({
  ...
  imports: [
    ...
    AngularRuntimeConfigModule.forRoot(Configuration, {
      urlFactory: () => [ 'config/config.common.json', 'config/config.DEV.json' ]
    })
  ],
}

Then request your Configuration class in any injection context.

@Injectable({...})
export class SomeService {
  constructor(private readonly config: Configuration) {}
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM