I have a little bootstrapping problem using Angular 4. I have a config file (json) that I'm loading from the server as my application starts (see also here). This works so far.
Now I have a dependency that requires config values to be passed in a forRoot method. The implementation looks like this:
static forRoot(config: AppInsightsConfig): ModuleWithProviders {
return {
ngModule: ApplicationInsightsModule,
providers: [
{ provide: AppInsightsConfig, useValue: config }
]
};
}
My Idea was to import the module without forRoot(), but providing the AppInsightsConfig by after the configs (from server) have been loaded.
@NgModule({
bootstrap: sharedConfig.bootstrap,
declarations: [...sharedConfig.declarations],
imports: [
BrowserModule,
FormsModule,
HttpModule,
ApplicationInsightsModule,
...sharedConfig.imports
],
providers: [
{ provide: 'ORIGIN_URL', useValue: location.origin },
{ provide:
APP_INITIALIZER,
useFactory: (configService: ConfigService) => () => configService.load(),
deps: [ConfigService], multi: true
},
{ provide:
AppInsightsConfig,
useFactory: (configService: ConfigService) => {
// outputs undefined, expected it to have the config
console.log(configService.config);
return { instrumentationKey: 'key from config' }
},
// My idea was, if I add APP_INITIALIZER as dependency,
// the config would have been loaded, but this isn't the case.
deps: [APP_INITIALIZER, ConfigService]
},
AppInsightsService
]
})
How can I provide AppInsightsConfig or another service AFTER my configs have been loaded?
Finally, I came up with following solution:
This is how my app-module looks like now:
import { Resolve, Router } from '@angular/router';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { sharedConfig } from './app.module.shared';
import { SiteControlModule } from './site-control/site-control.module';
import { SiteControlComponent } from './site-control/site-control.component';
import { AuthModule } from './auth/auth.module';
import { ConfigService } from './core/config/config.service';
import { ApplicationInsightsModule } from './application-insights/application-insights.module';
import { ApplicationInsightsService } from './application-insights/application-insights.service';
import { CoreModule } from './core/core.module';
let initializeConfig = (aiService: ApplicationInsightsService, configService: ConfigService) => () => {
let loadConfigPromise = configService.load();
loadConfigPromise.then(() => {
aiService.config = configService.config.applicationInsights;
aiService.init();
});
return loadConfigPromise;
};
@NgModule({
bootstrap: sharedConfig.bootstrap,
declarations: [...sharedConfig.declarations],
imports: [
BrowserModule,
FormsModule,
HttpModule,
AuthModule,
...sharedConfig.imports,
CoreModule.forRoot(),
ApplicationInsightsModule.forRoot(),
SiteControlModule
],
providers: [
{ provide: 'ORIGIN_URL', useValue: location.origin },
{ provide:
APP_INITIALIZER,
useFactory: initializeConfig,
deps: [ ApplicationInsightsService, ConfigService ],
multi: true
}
]
})
export class AppModule {
}
config.service.ts
import { Injectable } from '@angular/core';
import { Headers, RequestOptions, Http, Response} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/toPromise';
import { Config } from './models/config.model';
@Injectable()
export class ConfigService {
config : Config;
constructor(private http: Http) {
console.log('constructing config service');
}
public load(): Promise<void> {
let headers = new Headers({ 'Content-Type': 'application/json' });
let url = 'environment.json';
let promise = this.http.get(url, { headers })
.toPromise()
.then(configs => {
console.log('environment loaded');
this.config = configs.json() as Config;
})
.catch(err => {
console.log(err);
});
return promise;
}
}
Altered part of the application-insights.service.ts
import { Injectable, Optional, Injector } from '@angular/core';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
import { AppInsights } from 'applicationinsights-js';
import 'rxjs/add/operator/filter';
import IAppInsights = Microsoft.ApplicationInsights.IAppInsights;
import { ApplicationInsightsConfig } from '../core/config/models/application-insights.model';
@Injectable()
export class ApplicationInsightsService implements IAppInsights {
context: Microsoft.ApplicationInsights.ITelemetryContext;
queue: Array<() => void>;
config: Microsoft.ApplicationInsights.IConfig;
constructor(@Optional() _config: ApplicationInsightsConfig, private injector: Injector) {
this.config = _config;
}
private get router(): Router {
return this.injector.get(Router);
}
...
application-insights.module.ts
import { NgModule, ModuleWithProviders, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ApplicationInsightsService} from './application-insights.service';
export * from './application-insights.service';
@NgModule({
imports: [ CommonModule ],
declarations: [],
exports: [],
providers: []
})
export class ApplicationInsightsModule {
constructor(@Optional() @SkipSelf() parentModule: ApplicationInsightsModule) {
if (parentModule) {
throw new Error(
'ApplicationInsightsModule is already loaded. Import it in the AppModule only');
}
}
static forRoot(): ModuleWithProviders {
return {
ngModule: ApplicationInsightsModule,
providers: [ ApplicationInsightsService ]
};
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With