理解Angular的providers - 給Http添加默認headers

在通常的web應用裏,常常會須要在每次發送Http請求的時候,添加header或者一些默認的參數。本文就來看看這個需求的幾種實現方式。經過這個實現,咱們也可以理解Angular的服務,及其providers的原理。javascript

咱們的目的是對於每一個Http請求,都往Header裏面添加一個token,用於在服務器端進行身份驗證。由於Http是一個服務,因此我就想固然的想到,我能夠經過擴展框架提供的Http來添加。那麼要怎麼擴展一個框架提供的服務呢?那就是用providers。
NgModule裏,有一個屬性providers,通常咱們是用它來告訴框架,咱們的app要用到咱們定義的某些服務,例如我寫了一個UserService用來進行用戶數據的讀寫操做,又好比寫一個AuthGuardService來實現路由的Guard。對於框架或者使用的其餘組件庫的服務,咱們不須要在這裏添加,只須要在imports裏面加入相應的模塊便可。java

自定義系統服務

那麼,若是咱們想修改框架提供的某個服務,例如想擴展它,該怎麼實現呢?咱們能夠將擴展的這個服務,添加到providers裏,只是添加的方式不太同樣。須要使用下面的方式:web

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule, RouterModule, HttpModule
  ],
  providers: [UserService, AuthGuardService,
    { provide: Http, useClass: BaseHttp }
  ],
  bootstrap: [ AppComponent ]
})複製代碼

咱們擴展了Http服務,新的服務的類名是BaseHttp,而後在providers裏使用{ provide: Http, useClass: BaseHttp },告訴框架,咱們要使用BaseHttp這個類,來提供對Http的實現。而後,在Angular的容器裏面的Http服務其實是BaseHttp這個類的實現,當咱們經過注入得到一個Http實例的時候,也是得到的BaseHttp的實例。bootstrap

實現自動添加Header

接下來,咱們就來看看怎麼實現自動的Header的添加。首先,我想到的第一種方式,就是擴展Http,在它的構造函數裏設置一個默認的Header。服務器

在構造函數中實現

@Injectable()
export class BaseHttp extends Http {
  constructor (backend: XHRBackend, options: RequestOptions) {
    super(backend, options);

    let token = localStorage.getItem(AppConstants.tokenName);
    options.headers.set(AppConstants.authHeaderName, token);
  }
}複製代碼

這個就是在構造函數裏面,從localStorage裏拿到token,而後放到RequestOptions裏。看着彷佛沒有問題,可是運行的時候發現,這個Http服務是在app初始化的時候建立的,因此這個構造函數在調用的時候,localStorage裏可能尚未token。這樣,即便用戶以後登錄了,以前的默認的options也不會更新。app

在request中實現

因此,在構造函數中實現確定是不行的,我經過觀察Http的接口(經過你使用的IDE,能夠跟蹤到接口的定義文件,來查看接口的定義),看到有不少方法get(...), post(...), put(...)等,若是我須要從新實現全部的這些方法,那就太麻煩了,感受沒有必要。而後,我看到request(...)方法,看他的方法的註釋知道,全部其餘方法最終都會調用這個方法來發送實際的請求。因此,咱們只須要重寫這個方法就能夠:框架

@Injectable()
export class BaseHttp extends Http {
  constructor (backend: XHRBackend, options: RequestOptions) {
    super(backend, options)
  }

  request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
    const token = localStorage.getItem(AppConstants.tokenName)

    if (typeof url === 'string') { // meaning we have to add the token to the options, not in url
      if (!options) {
        options = new RequestOptions({})
      }
      options.headers.set(AppConstants.authHeaderName, token)
    } else {
      url.headers.set(AppConstants.authHeaderName, token)
    }
    return super.request(url, options)
  }
}複製代碼

這個實現也很容易,惟一須要說明的是,這裏的url它有可能有2種類型,stringRequest,若是是string類型,說明這個url就是一個字符串,那麼咱們要設置的header確定不會在它裏面。ide

那若是url是Request類型呢?咱們再來看看它的定義。經過看它的定義:函數

export declare class Request extends Body {
    /** * Http method with which to perform the request. */
    method: RequestMethod;
    /** * {@link Headers} instance */
    headers: Headers;
    /** Url of the remote resource */
    url: string;
    /** Type of the request body **/
    private contentType;
    /** Enable use credentials */
    withCredentials: boolean;
    /** Buffer to store the response */
    responseType: ResponseContentType;
    constructor(requestOptions: RequestArgs);
    /**
     * Returns the content type enum based on header options.
     */
    detectContentType(): ContentType;
    /**
     * Returns the content type of request's body based on its type.
     */
    detectContentTypeFromBody(): ContentType;
    /**
     * Returns the request's body according to its type. If body is undefined, return
     * null.
     */
    getBody(): any;
}複製代碼

咱們知道它是一個類,裏面有一個成員headers,而後咱們再看看Headers這個類型,看到它有一個set()方法,是用來往headers裏面添加值的,這正是咱們須要的。post

因此,在咱們實現的BaseHttp.request()方法裏,根據url的類型,再判斷options是否爲空等。經過測試,這種方法可以實現咱們的需求,不論是初始化的時候在localStorage裏面就有token,仍是以後登錄,甚至退出後更新再登陸(會更新localStorage的token)等,都能知足。

從新實現 RequestOptions

雖然上面的方法以及可以解決問題,那麼,能不能再簡單一點呢?由於咱們須要的只是更新Options,可是,爲了這個,咱們攔截了Http的請求。那咱們是否是能夠直接擴展RequestOptions來實現呢?答案是yes。並且更容易,咱們能夠繼承BaseRequestOptions,重寫merge(...)方法。

@Injectable()
export class AuthRequestOptions extends BaseRequestOptions {
  merge(options?: RequestOptionsArgs): RequestOptions {
    let newOptions = super.merge(options);
    let token = localStorage.getItem(AppConstants.tokenName);
    newOptions.headers.set(AppConstants.authHeaderName, token);
    return newOptions;
  }
}複製代碼

這個merge(...)方法會在每次請求的時候被調用,用來把請求的時候的options和默認options進行合併。

通過測試,這種方法也可以完美的解決咱們的需求。

總結

因此,這就是Angular強大與方便的地方,它使用了不少現象對象的特性,如繼承、接口、實現等;也用了不少服務器端Java框架的特性,例如容器等。上面說的provider也就是容器裏面對象實例的提供者,原本RequestOptions類型的提供者是BaseRequestOptions,可是,我繼承了它,重寫了一個方法,把這個類型的提供者改爲了我寫的類。這樣,Angular容器在初始化的時候,就會使用我提供的類來建立這個類型的實例。

並且,在這幾種實現方式的探索過程當中,我徹底沒有查看Angular的文檔,也沒有網上查什麼資料。知識查看類或接口的定義,經過它的註釋,我就有了思路,而後嘗試實現,就成功了。這也是TypeScript給我嗎帶來的遍歷。

相關文章
相關標籤/搜索