TL;DR, Angular support for one deployment package for different environments with specific configurations.
Angular framework lacks support for runtime configuration management. Build-in environments structure is compiled with the deployment package, so you have to build the application for every environment. It doesn't allow you to build one package which could be deployed to different environments.
This library brings support for loading configuration in runtime. It also allows you to load multiple configuration files and merge them into one configuration object.
At the initialisation phase of Angular app request for JSON configuration file is made. The content of the file is saved into configuration object that is resolvable by dependency injection.
By default, it looks for config.json file at the app root. But it can be changed to look for any other file,
or even for multiple files that will be merged into one configuration object. This way you can have some configuration
parameters shared between all environments and override only the specific parameters.
It can be as simple as:
@Injectable({...})exportclassSomeService{constructor(privatereadonlyconfig: Configuration,// <-- look hereprivatereadonlyhttp: HttpClient){}asyncgetData(): Promise<any>{constbaseUrl=this.config.apiUrl;// <-- look herevardata=awaitthis.http.get(apiUrl+'/data').toPromise();returndata;}}Install
angular-runtime-configlibrary.$ npm install angular-runtime-config
Create configuration class definition with your configuration parameters.
exportclassConfiguration{readonlyapiUrl!: string;// only examplereadonlyapiKey?: string;// only example// add some other configuration parameters}
Import
AngularRuntimeConfigModulein yourAppModule. You have to specify configuration class from previous step as a parameter forforRoot()method.import{AngularRuntimeConfigModule}from'angular-runtime-config'; @NgModule({declarations: [AppComponent],imports: [ ...,// specify AngularRuntimeConfigModule as an importAngularRuntimeConfigModule.forRoot(Configuration)],providers: [],bootstrap: [AppComponent]})exportclassAppModule{}
Create
config.jsonfile at the root of your app.{"apiUrl": "some url","apiKey": "some key"}
Add
config.jsonfile to assets inangular.json... "assets": ["src/favicon.ico","src/assets","src/config.json"// <-- this line], ...
Request your configuration class in any injection context.
@Injectable({...})exportclassSomeService{constructor(privatereadonlyconfig: Configuration){}}
With this basic usage it is the responsibility of deployment to change config.json appropriately.
If you want to make your deployment simple you can make the decision what configuration file to load in runtime based on some information (for example current app URL or a query string). For that look at the code examples next.
Configuration file URL can be absolute or relative to app root url.
AngularRuntimeConfigModule.forRoot(Configuration,{urlFactory: ()=>'config/config.json'})When using multiple configuration files, files are merged in returned array order.
AngularRuntimeConfigModule.forRoot(Configuration,{urlFactory: ()=>['config/config.common.json','config/config.DEV.json']})Don't forget to add all configuration files to assets in angular.json. You can also add whole folder.
...
"assets": ["src/favicon.ico","src/assets","src/config"// <-- adds whole folder],
...AngularRuntimeConfigModule.forRoot(Configuration,{urlFactory: ()=>{constenv=getEnvironment();// your defined method that provides current environment namereturn['/config/config.common.json',`/config/config.${env}.json`]}})Example of getEnvironment() function: (it can be implemented in any way)
functiongetEnvironment(): string{switch(location.origin){case'http://localhost': return'LOCAL';case'https://dev.example.com': return'DEV';case'https://int.example.com': return'INT';case'https://www.example.com': return'PROD';default: throwError('Unexpected base URL');}}If you need to resolve some dependencies in order to determine url of configuration files, you can use Angular Injector.
AngularRuntimeConfigModule.forRoot(Configuration,{urlFactory: (injector: Injector)=>{constenv=getEnvironment(injector);// your defined method that provides current environment namereturn['/config/config.common.json',`/config/config.${env}.json`]}})It is even possible to implement make urlFactory asynchronous.
AngularRuntimeConfigModule.forRoot(Configuration,{urlFactory: async(injector: Injector)=>{constenv=awaitgetEnvironment(injector);// your defined method that provides current environment namereturn['/config/config.common.json',`/config/config.${env}.json`]}})- Injection of
Configurationclass does not work inAPP_INITIALIZERS(error:Configuration hasn't been initialized).This is because configuration is also loaded within
APP_INITIALIZERSso otherAPP_INITIALIZERScannot depend onConfigurationclass. The solution is to replaceAPP_INITIALIZERSwithCONFIGURATION_APP_INITIALIZERSfor initializers that depend (even transitively) onConfigurationclass.{provide: CONFIGURATION_APP_INITIALIZER,useFactory: (config: Configuration)=>()=> ...,deps: [Configuration],multi: true}
MIT © Martin Volek