Reputation: 17671
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?
Upvotes: 4
Views: 1686
Reputation: 12872
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 ],
}
],
};
}
}
Upvotes: 2
Reputation: 40896
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()
.
Upvotes: 3