理解Angular的providers给Http添加默认headers


Posted in Javascript onJuly 04, 2017

在一般的web应用里,经常会需要在每次发送Http请求的时候,添加header或者一些默认的参数。本文就来看看这个需求的几种实现方式。通过这个实现,我们也能够理解Angular的服务,及其providers的原理。

我们的目的是对于每个Http请求,都往Header里面添加一个token,用于在服务器端进行身份验证。因为Http是一个服务,所以我就想当然的想到,我可以通过扩展框架提供的Http来添加。那么要怎么扩展一个框架提供的服务呢?那就是用providers。

NgModule里,有一个属性providers,一般我们是用它来告诉框架,我们的app要用到我们定义的某些服务,例如我写了一个UserService用来进行用户数据的读写操作,又比如写一个AuthGuardService来实现路由的Guard。对于框架或者使用的其他组件库的服务,我们不需要在这里添加,只需要在imports里面加入相应的模块即可。

自定义系统服务

那么,如果我们想修改框架提供的某个服务,例如想扩展它,该怎么实现呢?我们可以将扩展的这个服务,添加到providers里,只是添加的方式不太一样。需要使用下面的方式:

@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的实例。

实现自动添加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也不会更新。

在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种类型,string或Request,如果是string类型,说明这个url就是一个字符串,那么我们要设置的header肯定不会在它里面。

那如果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里面添加值的,这正是我们需要的。

所以,在我们实现的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给我吗带来的遍历。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JObj预览一个JS的框架
Mar 13 Javascript
document.forms用法示例介绍
Jun 26 Javascript
用Jquery.load载入页面后样式没了页面混乱的解决方法
Oct 20 Javascript
jQuery的框架介绍
May 11 Javascript
js操作数据库实现注册和登陆的简单实例
May 26 Javascript
JavaScript中匿名函数的用法及优缺点详解
Jun 01 Javascript
使用smartupload组件实现jsp+jdbc上传下载文件实例解析
Jan 05 Javascript
html5 canvas 详细使用教程
Jan 20 Javascript
使用react-router4.0实现重定向和404功能的方法
Aug 28 Javascript
JS交互点击WKWebView中的图片实现预览效果
Jan 05 Javascript
基于Express框架使用POST传递Form数据
Aug 10 Javascript
安装多版本Vue-CLI的实现方法
Mar 24 Javascript
详解在Angular项目中添加插件ng-bootstrap
Jul 04 #Javascript
详解在 Angular 项目中添加 clean-blog 模板
Jul 04 #Javascript
Node.js使用gm拼装sprite图片
Jul 04 #Javascript
JavaScript中document.referrer的用法详解
Jul 04 #Javascript
基于hover的用法实例(推荐)
Jul 04 #Javascript
js学习总结_基于数据类型检测的四种方式(必看)
Jul 04 #Javascript
DataTables添加额外的查询参数和删除columns等无用参数实例
Jul 04 #Javascript
You might like
php网页后退不再出现过期
2007/03/08 PHP
php自动加载机制的深入分析
2013/06/08 PHP
如何使用php判断服务器是否是HTTPS连接
2013/07/05 PHP
简单的php新闻发布系统教程
2014/05/09 PHP
Laravel中log无法写入问题的解决
2017/06/17 PHP
跨浏览器的设置innerHTML方法
2006/09/18 Javascript
jquery tab插件制作实现代码
2010/06/22 Javascript
jquery打开直接跳到网页最下面、最低端实现代码
2013/04/22 Javascript
JavaScript使用pop方法移除数组最后一个元素用法实例
2015/04/06 Javascript
JS或jQuery获取ASP.NET服务器控件ID的方法
2015/06/08 Javascript
基于JS实现新闻列表无缝向上滚动实例代码
2016/01/22 Javascript
VUE DOM加载后执行自定义事件的方法
2018/09/07 Javascript
关于layui的动态图标不显示的解决方法
2019/09/04 Javascript
vue实现文件上传读取及下载功能
2020/11/17 Javascript
在LayUI图片上传中,解决由跨域问题引起的请求接口错误的方法
2019/09/24 Javascript
非常漂亮的js烟花效果
2020/03/10 Javascript
Typescript3.9 常用新特性一览(推荐)
2020/05/14 Javascript
[02:50]【扭转乾坤,只此一招】DOTA2永雾林渊版本开启新篇章
2020/12/22 DOTA
初学python数组的处理代码
2011/01/04 Python
python模拟登录百度贴吧(百度贴吧登录)实例
2013/12/18 Python
Python基于Matplotlib库简单绘制折线图的方法示例
2017/08/14 Python
使用apidocJs快速生成在线文档的实例讲解
2018/02/07 Python
python 3.6.4 安装配置方法图文教程
2018/09/18 Python
python的列表List求均值和中位数实例
2020/03/03 Python
python3实现名片管理系统(控制台版)
2020/11/29 Python
详解html5页面 rem 布局适配方法
2018/01/12 HTML / CSS
美国新蛋IT数码商城:Newegg.com
2016/07/21 全球购物
英国领先的维生素和营养补充剂直接供应商:Healthspan
2019/04/22 全球购物
Jones Bootmaker官网:优质靴子和鞋子在线
2020/11/30 全球购物
外语专业毕业生个人的自荐信
2013/11/19 职场文书
师范学院毕业生求职信
2014/06/24 职场文书
区政府领导班子个人对照检查材料
2014/09/25 职场文书
向国旗敬礼活动小结
2014/09/27 职场文书
2014幼儿园大班工作总结
2014/11/10 职场文书
解决Nginx 配置 proxy_pass 后 返回404问题
2021/03/31 Servers
简单谈谈Python面向对象的相关知识
2021/06/28 Python