How to extend angular 2 http class in Angular 2 fi

2020-02-03 10:22发布

问题:

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!

回答1:

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.



回答2:

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


回答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.



回答4:

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



回答5:

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
]


回答6:

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