理解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 相关文章推荐
DOM下的节点属性和操作小结
May 14 Javascript
改善你的jQuery的25个步骤 千倍级效率提升
Feb 11 Javascript
jQuery中iframe的操作(点击按钮新增窗口)
Apr 20 Javascript
AngularJS模板加载用法详解
Nov 04 Javascript
自定义require函数让浏览器按需加载Js文件
Nov 24 Javascript
Angularjs实现搜索关键字高亮显示效果
Jan 17 Javascript
hammer.js实现图片手势放大效果
Aug 29 Javascript
javascript计算渐变颜色的实例
Sep 22 Javascript
js 将canvas生成图片保存,或直接保存一张图片的实现方法
Jan 02 Javascript
cocos2dx+lua实现橡皮擦功能
Dec 20 Javascript
vue+element模态框中新增模态框和删除功能
Jun 11 Javascript
详解vue中v-on事件监听指令的基本用法
Jul 22 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
真正面向对象编程:PHP5.01发布
2006/10/09 PHP
一个php作的文本留言本的例子(五)
2006/10/09 PHP
zend api扩展的php对象的autoload工具
2011/04/18 PHP
PHP实现HTML生成PDF文件的方法
2014/11/07 PHP
Yii框架在页面输出执行sql语句以方便调试的实现方法
2014/12/24 PHP
phpStudy访问速度慢和启动失败的解决办法
2015/11/19 PHP
php实现不通过扩展名准确判断文件类型的方法【finfo_file方法与二进制流】
2017/04/18 PHP
基于jquery的下拉框改变动态添加和删除表格实现代码
2020/09/12 Javascript
IE 当eval遇上function的处理
2011/08/09 Javascript
简单的jquery拖拽排序效果实现代码
2011/09/20 Javascript
javascript中的toFixed固定小数位数 简单实例分享
2013/07/12 Javascript
jquery上传插件fineuploader上传文件使用方法(jquery图片上传插件)
2013/12/05 Javascript
详解JavaScript UTC时间转换方法
2016/01/07 Javascript
javascript字符串对象常用api函数小结(连接,替换,分割,转换等)
2016/09/20 Javascript
jQuery弹出div层过2秒自动消失
2016/11/29 Javascript
js使用Replace结合正则替换重复出现的字符串功能示例
2016/12/27 Javascript
Vue中计算属性computed的示例解读
2017/07/26 Javascript
JS实现简单的表格增删
2020/01/16 Javascript
[05:39]2014DOTA2西雅图国际邀请赛 淘汰赛7月14日TOPPLAY
2014/07/14 DOTA
[49:08]FNATIC vs Infamous 2019国际邀请赛小组赛 BO2 第二场 8.16
2019/08/18 DOTA
Python数据结构与算法之图的基本实现及迭代器实例详解
2017/12/12 Python
对TensorFlow的assign赋值用法详解
2018/07/30 Python
使用11行Python代码盗取了室友的U盘内容
2018/10/23 Python
pygame游戏之旅 添加碰撞效果的方法
2018/11/20 Python
基于python图像处理API的使用示例
2020/04/03 Python
pandas to_excel 添加颜色操作
2020/07/14 Python
python中_del_还原数据的方法
2020/12/09 Python
C语言中一个结构不能包含指向自己的指针吗
2012/05/25 面试题
学生发电厂实习自我鉴定
2013/09/22 职场文书
机械工程系毕业生求职信
2013/09/27 职场文书
艺术设计专业个人求职信
2014/04/10 职场文书
学生会干部自我鉴定2014
2014/09/18 职场文书
院系推荐意见
2015/06/05 职场文书
浅谈Python项目的服务器部署
2021/04/25 Python
Python保存并浏览用户的历史记录
2022/04/29 Python
Vue ECharts实现机舱座位选择展示功能
2022/05/15 Vue.js