【问题标题】:Intercepting HTTP Response in Angular 2在 Angular 2 中拦截 HTTP 响应
【发布时间】:2016-09-12 00:29:51
【问题描述】:

我正在使用 RC6,并试图弄清楚如何在整个应用程序中捕获 HTTP 错误 - 特别是身份验证错误。

有许多帖子描述了如何使用自定义类扩展 Http 类,但我不确定如何注册新类,因为它看起来语法随着最近的 ngModule 更改而改变.

这是类(添加了所有相关的导入):

@Injectable()
export class InterceptedHttp extends Http {

 constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {
  super( backend, defaultOptions);
 }

 request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
  console.log('request...');
  return super.request(url, options);
 }

 get(url: string, options?: RequestOptionsArgs): Observable<Response> {
  console.log('get...');
  return super.get(url,options);
 }
}

我认为我可以在@ngModuleproviders 部分执行以下操作:

 imports: [ HttpModule, ... ],
 providers: [
    ... 

    InterceptedHttp,
    {provide: Http, useClass: InterceptedHttp },
    ConnectionBackend
 ],

但这只会给我带来一堆缺失的模块错误:

ERROR in [default] C:/WebConnectionProjects/AlbumViewer/Web/src/app/app.module.ts:64:10
Argument of type '{ imports: (ModuleWithProviders | typeof BrowserModule)[]; declarations: (typeof AlbumList | type...' is not assignable to parameter of type 'NgModuleMetadataType'.
  Types of property 'providers' are incompatible.
  Type '(typeof ConnectionBackend | typeof Album | typeof Artist | typeof Track | typeof AppConfiguration...' is not assignable to type 'Provider[]'.
  Type 'typeof ConnectionBackend | typeof Album | typeof Artist | typeof Track | typeof AppConfiguration ...' is not assignable to type 'Provider'.
  Type 'typeof ConnectionBackend' is not assignable to type 'Provider'.
  Type 'typeof ConnectionBackend' is not assignable to type 'FactoryProvider'.
  Property 'provide' is missing in type 'typeof ConnectionBackend'.

删除添加的行,一切正常。

那么,如何注册一个自定义的 Http 类呢?

【问题讨论】:

标签: angular angular2-services


【解决方案1】:

我对此的处理方式有所不同。我创建了一个HTTPService 类,它与内置的Http 交互,而不是扩展Http

@Injectable()
export class HttpService{
    constructor(private http:Http){}

    /** Wrapper for Http.get() that intercepts requests and responses */
    get(url:string, options?:RequestOptions):Observable<any>{

        //pre-screen the request (eg: to add authorization token)
        options = this.screenRequest(options);

        return this.http.get(url,options)
            .map(res => res.json()) //my back-end return a JSON. Unwrap it
            .do(res => this.screenResponse(res)) // intercept response
            .catch(res => this.handleError(res));// server returned error status
    }

    /** similar to the above; a wrapper for Http.post() */
    post(url:string, body:string ,options?:RequestOptions):Observable<any>{}

    /** edits options before the request is made. Adds auth token to headers.*/
    screenOptions(options?:RequestOptions):RequestOptions{}

    /** Called with server's response. Saves auth token from the server */
    screenResponse(jsonResponse:any){}

    /** Called when server returns a 400-500 response code */
    handleError(response:Response){}        
}

所以我的代码从不直接调用 Angular 的 Http。相反,我打电话给HttpService.get()

【讨论】:

  • 是的,我也想到了这一点,这可能不是一个坏方法。就像其他解决方案一样,我讨厌请求和响应操作没有单点拦截。
  • 好的,所以我尝试了这个,但不知何故,当我实现 .catch() 时,我无法返回 observable。我得到一个类型不匹配:zone.js:344 Unhandled Promise reject: this.http.get(...).catch is not a function。这是实际代码的要点:gist.github.com/RickStrahl/204de6f9e6607b79010f5e7648b0d1a7
  • 我无法确定在您的要点末尾返回的是哪个函数,但您为什么要调用 this.http.get?你应该调用你的包装函数(例如:this.get())并且只有 it 应该可以访问 Angular Http 服务。
【解决方案2】:

我采用了不同的方法并扩展了XHRBackend,到目前为止它已经满足了我的所有需求。

export class CoreXHRBackend extends XHRBackend {

    constructor(xhr:BrowserXhr, opts:ResponseOptions, strat:XSRFStrategy, public alerts:Alerts) {
        super(xhr, opts, strat);
    }

    createConnection(request:Request) {
        let xhr = super.createConnection(request);

        /**
         * Global error handler for http requests
         */
        xhr.response = xhr.response.catch((error:Response) => {

            if (error.status === 401 && window.location.pathname !== '/') {
                this.alerts.clear().flash('You are not authorized to access that page', 'danger');
                window.location.href = '/';
            }

            if (error.status === 404) {
                this.alerts.clear().error('Sorry, we couldn\'t find that...');
            }

            // Validation errors or other list of errors
            if (error.status === 422) {
                var messages = error.json();
                Object.keys(messages).map(k => this.alerts.error(messages[k]));
            }

            if (error.status === 500) {
                this.alerts.clear().error('Sorry Something Went Wrong, Try Again Later!');
            }

            return Observable.throw(error);
        });

        return xhr;
    }
}

我还需要注入我的自定义警报服务并且构造函数不可注入,所以我在我的模块中这样处理...

export class CoreModule {
    static forRoot(): ModuleWithProviders {
        return {
            ngModule: CoreModule,
            providers: [
                Alerts,
                {
                    provide: XHRBackend,
                    useFactory: (xhr, opts, strat, alerts) => {
                        return new CoreXHRBackend(xhr, opts, strat, alerts);
                    },
                    deps: [ BrowserXhr, ResponseOptions, XSRFStrategy, Alerts ],
                }
            ],
        };
    }
}

【讨论】:

  • 我喜欢这个想法,但是在 AppModule 中设置它时似乎无法让它工作。我在根模块中添加了提供程序,但我得到:compiler.umd.js:8121Uncaught Error: Provider parse errors: Cannot instantiate cyclic dependency! Http:在 NgModule AppModule 中 - 我注入了不同的服务,但逻辑相同。
  • 您是否将该模块导入到多个模块中?
  • 嗯...我正在将 http 导入 AppModule。但我猜其中一个也正在导入它,这就是问题所在?如果是这样的话,这似乎很疯狂,因为我们无法控制其他模块使用什么。
  • 好吧,我看不到您的代码,但我猜您需要 XHRBackend 提供程序的单例实现。这就是为什么我在一个单独的模块中使用forRoot 并确保我只提供一个实例。
  • 我的 AppModule 使用 imports: [ CoreModule.forRoot() ]
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-07-08
  • 2016-08-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-13
  • 2018-01-12
相关资源
最近更新 更多