1

I want to override Http with a custom class, say HttpClient to do some operations before a request and after a response, however I don't want to have to remember to import that class instead of the platform http class.

I've been experimenting with doing this via the 'multi' providers, but I can't quite make it click.

Here's my override class:

import {Injectable} from '@angular/core';
import {Http, Headers} from '@angular/http';

@Injectable()
export class HttpClient {
  private http: Http;

  constructor(http: Http) {
    this.http = http;
  }

  get(url) {
    let headers = new Headers();
    doSomethingToHeader(headers);
    return this.http.get(url, {
      headers: headers
    });
  }
}

And here's my main.ts

bootstrap(AppComponent, [
  provide(HTTP_PROVIDERS, {useExisting: HTTP_PROVIDERS, multi: true}),
  provide(HTTP_PROVIDERS, {useClass: HttpClient, multi: true})
]);

But when I try to call http.get() in my app, I get

ORIGINAL EXCEPTION: No provider for Http!

Any ideas? Am I going about this the wrong way?

Thanks!

UPDATE

I found this blog post. It pretty much describes what Gunter describes below. I'm accepting his answer.

D_Naish
  • 543
  • 4
  • 20

1 Answers1

2

multi: true only works when the provider is designed to be a multi provider like PLATFORM_DIRECTIVES. HTTP_PROVIDERS are not multi providers, it's just a collection of individual providers.

What you can do is

bootstrap(AppComponent, [HTTP_PROVIDERS, {provide: Http, useClass: HttpClient}])

results in cycles (see comments)

This should work:

bootstrap(AppComponent, [
  HTTP_PROVIDERS, 
  {
    provide: Http, 
    useFactory: (backend, defaultOptions) => new HttpClient(new Http(backend, defaultOptions)),
    deps: [XHRBackend, RequestOptions]
  }
])

The important part is that your custom provider that overrides the provider defined in HTTP_PROVIDERS comes after the one you want to override. Therefore it's important that HTTP_PROVIDERS comes before {provide: Http, ...}

With multi: true you don't override a provider but instead add another item to a provider that provides a set of values.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I've tried that, but I get a cyclic dependency. Is there a special way you have to import it to use this.http.get() in a service/component after you've declared the provider like this? – D_Naish Jul 31 '16 at 22:08
  • Please provide more details. What is the exact error message? – Günter Zöchbauer Aug 01 '16 at 04:54
  • ORIGINAL EXCEPTION: Cannot instantiate cyclic dependency! (Http -> Http) – D_Naish Aug 01 '16 at 13:58
  • Right, that's actually to be expected. I guess it would be better to not provide it as `Http` but just `bootstrap(AppComponent, [HTTP_PROVIDERS, HttpClient])` and then inject it as `contructor(private http:HttpClient)`, otherwise you would need a factory that creates an `Http` instance with `new Http(....)` – Günter Zöchbauer Aug 01 '16 at 14:02
  • :*( Ok. Thanks. My worry though is that on a large team, ppl may forget/not-know to use the HttpClient. Was hoping to just avoid that by overriding Http in the bootstrap. – D_Naish Aug 01 '16 at 14:15
  • I see. I updated my answer. Not tested but I think it should work. – Günter Zöchbauer Aug 01 '16 at 14:19
  • No dice. I get `No provider for ConnectionBackend! (Http -> ConnectionBackend)`. – D_Naish Aug 01 '16 at 15:54
  • Can you please try to replace `ConnectionBackend` with `XHRBackend` (in `deps`)? – Günter Zöchbauer Aug 01 '16 at 15:56
  • That worked! Thank you very much Gunter! I also found an article right as we arrived here that describes this strategy. Link added to OP. – D_Naish Aug 01 '16 at 16:02