在通常的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给我吗带来的遍历。