简体   繁体   中英

Intercepting HTTP Response in Angular 2

I'm using RC6 and I'm trying to figure out how to catch HTTP errors - auth errors in particular - in the entire application.

There are a number of posts that describe how to extend the Http class with a custom class, but I'm not sure how to register the new class exactly as it appears the syntax has changed with the the recent ngModule changes.

Here's the class (with all relevant imports added):

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

I thought I'd be able to do the following in the providers section of @ngModule :

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

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

but this just gets me a bunch of missing module errors:

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

removing the added lines and everything works.

So, how do I register a custom Http class?

My approach to this has been different. I created an HTTPService class that interacts with the built-in Http , instead of extending 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){}        
}

So my code never calls Angular's Http directly. Instead, I call HttpService.get() .

I took a different approach and extended the XHRBackend and so far it's handled all my needs.

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

I also needed to inject my custom alerts service and the constructor isn't injectable so I handled that in my module like this...

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 ],
                }
            ],
        };
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM