在通常的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的添加。首先,我想到的第一種方式,就是擴展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
因此,在構造函數中實現確定是不行的,我經過觀察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種類型,string
或Request
,若是是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)等,都能知足。
雖然上面的方法以及可以解決問題,那麼,能不能再簡單一點呢?由於咱們須要的只是更新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給我嗎帶來的遍歷。