I\'m trying to extent angular 2 http class to be able to handle global errors and set up headers for my secureHttp service. I found some solutions but it doesn\'t work with
From Angular 4.3, we don't need to extends http
anymore. Instead, we can use HttpInterceptor
and HttpClient
to archive all these stuffs.
It's similar and easier than using Http
.
I migrated to HttpClient in about 2 hours.
Detail is here
The reason for the error is because you are trying to provide SecureHttpService
providers: [SecureHttpService]
What this means is that Angular will try to create the instance, not using your factory. And it doesn't have a provider registered with the token ConnectionBackend
to give to your constructor.
You could remove the SecureHttpService
from the providers
, but that will give you another error (which I'm guessing is why you added it in the first place). The error will be something like "no provider for SecureHttpService" because you are trying to inject it into your constructor
constructor(private titleService: Title, private _secure: SecureHttpService) {}
Here's where you need to understand about tokens. What you provide as the value to provide
is the token.
{
provide: Http,
useFactory: ()
}
The token is what we are allowed to inject with. So you can instead inject the Http
and it will use your created SecureHttpService
. But this will take away any chance you have of using the regular Http
, if you ever need it.
constructor(private titleService: Title, private _secure: Http) {}
If you don't need to know anything about the SecureHttpService
, then you can leave it like this.
If you want to be able to actually inject the SecureHttpService
type (maybe you need some API from it or maybe you want to be able to use the normal Http
elsewhere), then just change the provide
{
provide: SecureHttpService,
useFactory: ()
}
Now you can inject both the regular Http
and your SecureHttpService
. And don't forget to remove the SecureHttpService
from the providers
.
You can actually just extend the Http in your own class and then the only use a custom factory to provide Http as that:
then in my App Providers I was able to use a custom Factory to provide 'Http'
import { RequestOptions, Http, XHRBackend} from '@angular/http';
class HttpClient extends Http {
/*
insert your extended logic here. In my case I override request to
always add my access token to the headers, then I just call the super
*/
request(req: string|Request, options?: RequestOptionsArgs): Observable<Response> {
options = this._setCustomHeaders(options);
// Note this does not take into account where req is a url string
return super.request(new Request(mergeOptions(this._defaultOptions,options, req.method, req.url)))
}
}
}
function httpClientFactory(xhrBackend: XHRBackend, requestOptions: RequestOptions): Http {
return new HttpClient(xhrBackend, requestOptions);
}
@NgModule({
imports:[
FormsModule,
BrowserModule,
],
declarations: APP_DECLARATIONS,
bootstrap:[AppComponent],
providers:[
{ provide: Http, useFactory: httpClientFactory, deps: [XHRBackend, RequestOptions]}
],
})
export class AppModule {
constructor(){
}
}
with this approach you don't need to override any of the Http function you do not wish to change
Check out my article on how to extend the Http class for Angular 2.1.1
First, lets create our custom http provider class.
http.service.ts
import {Injectable} from '@angular/core';
import {Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
@Injectable()
export class HttpService extends Http {
constructor (backend: XHRBackend, options: RequestOptions) {
let token = localStorage.getItem('auth_token'); // your custom token getter function here
options.headers.set('Authorization', `Bearer ${token}`);
super(backend, options);
}
request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
let token = localStorage.getItem('auth_token');
if (typeof url === 'string') { // meaning we have to add the token to the options, not in url
if (!options) {
// let's make option object
options = {headers: new Headers()};
}
options.headers.set('Authorization', `Bearer ${token}`);
} else {
// we have to add the token to the url object
url.headers.set('Authorization', `Bearer ${token}`);
}
return super.request(url, options).catch(this.catchAuthError(this));
}
private catchAuthError (self: HttpService) {
// we have to pass HttpService's own instance here as `self`
return (res: Response) => {
console.log(res);
if (res.status === 401 || res.status === 403) {
// if not authenticated
console.log(res);
}
return Observable.throw(res);
};
}
}
Now, we need to configure our main module to provide the XHRBackend to our custom http class. In your main module declaration, add the following to the providers array:
app.module.ts
import { HttpModule, RequestOptions, XHRBackend } from '@angular/http';
import { HttpService } from './services/http.service';
...
@NgModule({
imports: [..],
providers: [
{
provide: HttpService,
useFactory: (backend: XHRBackend, options: RequestOptions) => {
return new HttpService(backend, options);
},
deps: [XHRBackend, RequestOptions]
}
],
bootstrap: [ AppComponent ]
})
After that, you can now use your custom http provider in your services. For example:
user.service.ts
import { Injectable } from '@angular/core';
import {HttpService} from './http.service';
@Injectable()
class UserService {
constructor (private http: HttpService) {}
// token will added automatically to get request header
getUser (id: number) {
return this.http.get(`/users/${id}`).map((res) => {
return res.json();
} );
}
}
I think peeskillet's answer should be the selected answer, so what I'm putting here is only meant to augment his answer as opposed to compete with it, but I also wanted to provide a concrete example since I don't think it's 100% obvious exactly what code peeskillet's answer translates to.
I put the following in the providers
section of my app.module.ts
. I'm calling my custom Http
replacement MyHttp
.
Notice how, like peeskillet said, it would be provide: Http
, not provide: MyHttp
.
providers: [
AUTH_PROVIDERS
{
provide: Http,
useFactory: (backend: XHRBackend, defaultOptions: RequestOptions) => {
return new MyHttp(backend, defaultOptions);
},
deps: [XHRBackend, RequestOptions]
}
],
Then my Http
-extending class is defined like this:
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
@Injectable()
export class MyHttp extends Http {
get(url: string, options?: any) {
// This is pointless but you get the idea
console.log('MyHttp');
return super.get(url, options);
}
}
Nothing special needs to be done in order for your app to use MyHttp
instead of Http
.
You can check https://www.illucit.com/blog/2016/03/angular2-http-authentication-interceptor/ which will help you.
Change your providers as below for latest release and check it :
providers: [
{
provide: SecureHttpService,
useFactory: (backend: XHRBackend, defaultOptions: RequestOptions) => {
return new SecureHttpService(backend, defaultOptions);
},
deps: [ XHRBackend, RequestOptions]
},
Title
]