24

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 final release of Angular 2. There is my code:

File: secureHttp.service.ts

import { Injectable } from '@angular/core';
import { Http, ConnectionBackend, Headers, RequestOptions, Response, RequestOptionsArgs} from '@angular/http';

@Injectable()
export class SecureHttpService extends Http {

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

File: app.module.ts

    import { BrowserModule, Title } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { routing } from './app.routes';
import { AppComponent } from './app.component';
import { HttpModule, Http, XHRBackend, RequestOptions } from '@angular/http';
import { CoreModule } from './core/core.module';
import {SecureHttpService} from './config/secure-http.service'

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    CoreModule,
    routing,
    HttpModule,
  ],
  providers: [
    {
      provide: Http,
      useFactory: (backend: XHRBackend, defaultOptions: RequestOptions) => {
        return new SecureHttpService(backend, defaultOptions);
      },
      deps: [ XHRBackend, RequestOptions]
    }, Title, SecureHttpService],
  bootstrap: [AppComponent],
})
export class AppModule { }

component.ts

constructor(private titleService: Title, private _secure: SecureHttpService) {}

  ngOnInit() {
    this.titleService.setTitle('Dashboard');
    this._secure.get('http://api.example.local')
        .map(res => res.json())
        .subscribe(
            data =>  console.log(data) ,
            err => console.log(err),
            () => console.log('Request Complete')
        );
  }

For now it returns me an error 'No provider for ConnectionBackend!'. Thanks for help!

6 Answers 6

22

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.

Sign up to request clarification or add additional context in comments.

3 Comments

I logged back into my account after like a year just to vote this answer up. Thank you!
I get "Expression expected." error on {provide: HttpService, useFactory: ()}, code in app.module.ts
@PavelChuchuva You need to extract it into a function and use the function. This only happens with AOT. See update in this post
21

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

5 Comments

did you ever test your own code? one cannot call constructor(..) { statements; super(...); }. super() HAS to be the first statement when extending another class
I did. Though I am not aware of that, but this code is part of my app and it's working.
interesting, for me it didnt. However, if super().. comes first, everything works perfectly.
Am getting error "1:25 Error: Error: Can't resolve all parameters for CoinService: (?). at CompileMetadataResolver.getDependenciesMetadata".. Ideally "super" should come after the options parameter is updated. However for me neither works
We are experiencing some issues with this solution and U.T, this is not compatible with MockBackend class due to use of XhrBackend instead of ConnectionBackend.
3

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.

1 Comment

Fabulous.. Works like a breeze and actually feels like an injector. Entire application code also remains untouched and they feel as if using Http when in fact they are using our extended version (as your last line says)
2

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

Comments

0

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
]

5 Comments

Hi ranakrunal9! I saw it. This is a code for RC1 and it doesn't work with Final release. But thank you anyway:)
you have to change the path of used imported classes as may be in final release path is changed.
Path to imported classes is right. I've check it. I think problem some where in app.module in providers... But I'm not sure..
No.. It doesn't work... Still "No provider for ConnectionBackend!" I've updated question and provide whole code of my app.module file. Might be you will find something.
Remove SecureHttpService from your providers and that should remove the ConnectionBackend error
0

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

4 Comments

where is mergeOptions definition?
@Lyoneel its a basic function that just mergers options, nothing special about it - have a look at Angular source code for theirs github.com/angular/angular/blob/master/modules/%40angular/http/…
yes, thats correct i check http.umd.js and its there, my ide is not parsing stuff well.
As of todays Angular4 version, the fact of declaring the provider as a function - as opposed to a lambda, works better. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.