如何在Angular2中同时使用httpInterceptor和configService?

如何在Angular2中同时使用httpInterceptor和configService?

问题描述:

我的问题与DI with cyclic dependency with custom HTTP and ConfigService类似,但不完全相同,为此问题提供的解决方案并不能解决问题。如何在Angular2中同时使用httpInterceptor和configService?

我正在使用httpInterceptor并且还需要configService在运行时拉取环境信息。下面是它的样子:

//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); 
       } 
      }); 

     }); 
    } 
} 

这里是拦截

//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'); 
    } 

} 

这里是我得到

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 
错误

如果我需要提供更多信息,请让我知道形成。

我想出了一个办法来解决这个问题,这个我是如何注入的AppConfig

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'; 
 

 
export function createAppModule(conf) { 
 
@NgModule({ 
 
    declarations: [ 
 
    AppComponent 
 
    ], 
 

 
    imports: [ 
 
    BrowserModule, 
 
    FormsModule, 
 
    HttpModule, 
 
    routing, 
 
    ReactiveFormsModule 
 
    ], 
 
    providers: [  
 
    CacheService, 
 
    { provide: Config, useValue: conf }, 
 
    { 
 
     provide: AppConfig, 
 
     useFactory: (_config: Config) => { 
 
     return new AppConfig(_config); 
 
     }, deps: [Config] 
 
    }, 
 
    { 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] 
 
}) 
 
class AppModule { } 
 
return AppModule; 
 
}

我main.ts使用的AppModule和拉动config.json所做的更改,这里怎么看起来

import './polyfills.ts'; 
 
import { ReflectiveInjector, Injectable, OpaqueToken, Injector, NgModule, enableProdMode } from '@angular/core'; 
 
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 
 

 
import { 
 
    Http, CookieXSRFStrategy, XSRFStrategy, RequestOptions, BaseRequestOptions, 
 
    ResponseOptions, BaseResponseOptions, XHRBackend, BrowserXhr, Response 
 
} from '@angular/http'; 
 

 
import { createAppModule } from './app/app.module'; 
 

 
function getHttp(): Http { 
 
    let providers = [ 
 
    { 
 
     provide: Http, useFactory: (backend: XHRBackend, options: RequestOptions) => { 
 
     return new Http(backend, options); 
 
     }, 
 
     deps: [XHRBackend, RequestOptions] 
 
    }, 
 
    BrowserXhr, 
 
    { provide: RequestOptions, useClass: BaseRequestOptions }, 
 
    { provide: ResponseOptions, useClass: BaseResponseOptions }, 
 
    XHRBackend 
 
    ]; 
 
    return ReflectiveInjector.resolveAndCreate(providers).get(Http); 
 
} 
 

 
getHttp().get('/assets/config.json').toPromise() 
 
    .then((res: Response) => { 
 
    let conf = res.json(); 
 
    platformBrowserDynamic().bootstrapModule(createAppModule(conf)); 
 
    }) 
 
    .catch(error => { console.error(error) });

可能有更好的方法做到这一点,但我发现这对我有用,但任何人都有更好的方法和任何建议,那么请提供您的意见。