欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-05-26 08:25:31
在一般的web应用里,经常会需要在每次发送http请求的时候,添加header或者一些默认的参数。本文就来看看这个需求的几种实现方式。通过这个实现,我们也能够理解angul...

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

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