My Question is similar to this DI with cyclic dependency with custom HTTP and ConfigService but not exactly same and solution provided for this question does not solve the issue for me.
I am using httpInterceptor and also need configService to pull environment info at run time. Here is how it looks like:
//app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER, Injector } from '@angular/core';
import { Router } from '@angular/router';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpModule, Http, Request, RequestOptionsArgs, Response, XHRBackend, RequestOptions, ConnectionBackend, Headers } from '@angular/http';
import { CacheService, CacheStoragesEnum } from 'ng2-cache/ng2-cache';
import { AppConfig } from './app.config';
import { AppComponent } from './app.component';
import { HttpInterceptorService} from './services/http-interceptor/http-interceptor.service';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
routing,
ReactiveFormsModule
],
providers: [
CacheService,
AppConfig,
{ provide: APP_INITIALIZER, useFactory: (config: AppConfig) => config.load(), deps: [AppConfig]},
{ provide: Http, useFactory: (backend: ConnectionBackend, defaultOptions: RequestOptions, router: Router, cacheService: CacheService, appConfig: AppConfig) => return new HttpInterceptorService(backend, defaultOptions, router, cacheService, appConfig), deps: [XHRBackend, RequestOptions, Router, CacheService, AppConfig]}
],
bootstrap: [AppComponent]
})
export class AppModule { }
app.config.ts
//app.config.ts
import { Inject, Injectable, Injector } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Rx';
@Injectable()
export class AppConfig {
private config: Object = null;
private env: Object = null;
private http: Http;
constructor(private injector : Injector) {
setTimeout(() => {
console.log('initialize http in appConfig');
this.http = this.injector.get(Http);
});
}
/**
* Use to get the data found in the second file (config file)
*/
public getConfig(key: any) {
return this.config[key];
}
/**
* Use to get the data found in the first file (env file)
*/
public getEnv(key: any) {
return this.env[key];
}
/**
* This method:
* a) Loads "env.json" to get the current working environment (e.g.: 'production', 'development')
* b) Loads "config.[env].json" to get all env's variables (e.g.: 'config.development.json')
*/
public load() {
console.log('---------------------- in appConfig load method --------------------');
return new Promise((resolve, reject) => {
this.http.get('app/env.json').map( res => res.json() ).catch((error: any):any => {
console.log('Configuration file "env.json" could not be read');
resolve(true);
return Observable.throw(error.json().error || 'Server error');
}).subscribe( (envResponse) => {
this.env = envResponse;
console.log('-------------------------------------------------');
console.log(envResponse);
let request:any = null;
switch (envResponse['env']) {
case 'production': {
request = this.http.get('app/config.' + envResponse['env'] + '.json');
} break;
case 'test': {
request = this.http.get('app/config.' + envResponse['env'] + '.json');
} break;
case 'default': {
console.error('Environment file is not set or invalid');
resolve(true);
} break;
}
if (request) {
request
.map( res => res.json() )
.catch((error: any) => {
console.error('Error reading ' + envResponse['env'] + ' configuration file');
resolve(error);
return Observable.throw(error.json().error || 'Server error');
})
.subscribe((responseData) => {
this.config = responseData;
resolve(true);
});
} else {
console.error('Env config file "env.json" is not valid');
resolve(true);
}
});
});
}
}
And here is interceptor
//http-interceptor.service.ts
import {Http, Request, RequestOptionsArgs, Response, XHRBackend, RequestOptions, URLSearchParams,ConnectionBackend, Headers} from '@angular/http';
import {Router} from '@angular/router';
import { Observable } from 'rxjs/Observable';
import {CacheService, CacheStoragesEnum} from 'ng2-cache/ng2-cache';
import { AppConfig } from '../../app.config';
const TOKEN_EXPIRY = '1440';
const CACHE_EXPIRY = 10 * 60 * 1000;
const CACHE_RECYCLE = 4 * 60 * 1000;
export class HttpInterceptorService extends Http {
private webUrl : string;
private serviceUrl : string;
constructor(backend: ConnectionBackend,
defaultOptions: RequestOptions,
private _router: Router,
private _cacheService: CacheService,
private config: AppConfig) {
super(backend, defaultOptions);
this._cacheService = this._cacheService.useStorage(CacheStoragesEnum.LOCAL_STORAGE);
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return this.getToken(url).flatMap(tokenRecord =>super.request(url, this.getRequestOptionArgs(tokenRecord, options)));
}
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.getToken(url).flatMap(tokenRecord => super.get(url, this.getRequestOptionArgs(tokenRecord, options)))
}
post(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.getToken(url).flatMap(tokenRecord => super.post(url, body, this.getRequestOptionArgs(tokenRecord, options)))
}
put(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
return this.getToken(url).flatMap(tokenRecord => super.put(url, body, this.getRequestOptionArgs(tokenRecord, options)));
}
delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.getToken(url).flatMap(tokenRecord => super.delete(url, this.getRequestOptionArgs(tokenRecord, options)));
}
getRequestOptionArgs(tokenRecord, options?: RequestOptionsArgs) : RequestOptionsArgs {
if (options == null) {
options = new RequestOptions();
}
if (options.headers == null) {
options.headers = new Headers();
}
options.headers.append('Content-Type', 'application/json');
options.headers.append('Authorization', tokenRecord.token);
return options;
}
getToken (url) {
let domain;
this.webUrl = this.config.getConfig('webUrl');
this.serviceUrl = this.config.getConfig('serviceUrl');
if (this.serviceUrl.indexOf("://") > -1) {
domain = this.serviceUrl.split('/')[2];
} else {
domain = this.serviceUrl.split('/')[0];
}
domain = domain.split(':')[0];
let serviceHost = domain;
let headers = new Headers();
headers.append('Accept', 'application/json, text/plain, */*');
let params = new URLSearchParams();
params.append('service', serviceHost);
params.append('expiry', TOKEN_EXPIRY);
let options = new RequestOptions({ headers:headers, search:params, withCredentials:true });
let cacheName = "webCache";
let cacheKeyBase = "/token";
let cacheKey = cacheKeyBase + "/" + serviceHost;
let existingCache = this._cacheService.get(cacheKey);
if(existingCache) {
return Observable.of(existingCache);
}
return super.get(this.webUrl+'/a/token', options)
.map((response:Response) => {
response = response.json();
let tokenType = response['tokenType'];
let tokenVal = response['access_token'];
let token = tokenType + " " + tokenVal;
let tokenRecord = {
token:token,
tokenType:response['tokenType'],
expiresAt: new Date(response['expires_at'] * 1000),
expiresIn: response['expires_in'],
aquiredOn: new Date()
};
this._cacheService.set(cacheKey, tokenRecord, {maxAge: CACHE_EXPIRY});
return tokenRecord;
}).catch(this.handleError);
}
private handleError(error:Response){
return Observable.throw(error || 'Server error');
}
}
Here is the error I am getting
Unhandled Promise rejection: Cannot read property 'componentTypes' of undefined ; Zone: <root> ; Task: Promise.then ; Value: TypeError: Cannot read property 'componentTypes' of undefined(…) TypeError: Cannot read property 'componentTypes' of undefined
at setupRouter
Please let me know if I need to provide more information.