SoFunction
Updated on 2025-03-03

Understand Angular providers to add default headers to Http

In general web applications, it is often necessary to add header or some default parameters every time an Http request is sent. This article will take a look at several ways to implement this requirement. Through this implementation, we can also understand Angular's services and its principles of providers.

Our goal is to add a token to the header for each Http request to authenticate on the server side. Because Http is a service, I took it for granted that I could provide it through the extension frameworkHttpTo add. So how do we extend the services provided by a framework? That is to use providers.

existNgModuleThere is an attribute inprovidersGenerally, we use it to tell the framework that our app needs to use certain services we define, for example, I wrote aUserServiceUsed to read and write user data, for example, write aAuthGuardServiceTo implement routingGuard. For frameworks or other component libraries used, we do not need to add them here, we only need toimportsJust add the corresponding module.

Custom system services

So, how can we implement it if we want to modify a service provided by the framework, such as if we want to extend it? We can add this service toprovidersIn, the way it is added is different. The following method is required:

@NgModule({
 declarations: [
  AppComponent
 ],
 imports: [
  BrowserModule, RouterModule, HttpModule
 ],
 providers: [UserService, AuthGuardService,
  { provide: Http, useClass: BaseHttp }
 ],
 bootstrap: [ AppComponent ]
})

We've expandedHttpThe new service class name isBaseHttp, and thenprovidersUsed in{ provide: Http, useClass: BaseHttp }, tell the framework that we want to useBaseHttpThis class provides the rightHttpImplementation of . Then, the Http service inside the Angular container is actuallyBaseHttpThis class implementation, when we obtain aHttpWhen it comes to examples, it is also obtainedBaseHttpExamples of .

Implement automatic header addition

Next, let’s take a look at how to implement automatic header addition. First of all, the first way I think of is to extend Http and set a default header in its constructor.

Implemented in constructor

@Injectable()
export class BaseHttp extends Http {
 constructor (backend: XHRBackend, options: RequestOptions) {
  super(backend, options);
  let token = ();
  (, token);
 }
}

This is to get the token from localStorage in the constructor and put it in RequestOptions. It seems that there is no problem, but when running, it is found that the Http service was created when the app is initialized, so when this constructor is called, there may not be tokens in localStorage. In this way, even if the user logs in later, the previous default options will not be updated.

Implemented in request

Therefore, it is definitely not possible to implement it in the constructor. I saw that there are many methods by observing the interface of Http (through the IDE you use, you can track the interface definition file to view the interface definition).get(...), post(...), put(...)Wait, if I need to reimplement all of these methods, it would be too troublesome and feel unnecessary. Then, I sawrequest(...)Method, look at the comments of his method, know that all other methods will eventually call this method to send the actual request. So, we just need to rewrite this method:

@Injectable()
export class BaseHttp extends Http {
 constructor (backend: XHRBackend, options: RequestOptions) {
  super(backend, options)
 }
 request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
  const token = ()
  if (typeof url === 'string') { // meaning we have to add the token to the options, not in url
   if (!options) {
    options = new RequestOptions({})
   }
   (, token)
  } else {
   (, token)
  }
  return (url, options)
 }
}

This implementation is also easy. The only thing to be noted is that there may be two types of url here, string or Request. If it is a string type, it means that this url is a string, and the header we want to set will definitely not be inside it.

If url isRequestWhat about the type? Let's take a look at its definition. By looking at its definition:

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;
}

We know it is a class with a member inside itheaders, and then let's take a lookHeadersThis type, when you see that it has a set() method, it is used to add values ​​to headers, which is exactly what we need.

So, in our implementation()In the method, based on the type of url, then determine whether options are empty or not. Through testing, this method can achieve our needs. Whether it is the token in localStorage during initialization, logging in later, or even logging out after logging in (the token of localStorage will be updated), etc., it can be met.

Reimplement RequestOptions

Although the above method and can solve the problem, can it be simpler? Because all we need is to update Options, however, for this, we intercepted the Http request. Can we directly expandRequestOptionsWhat to do? The answer is yes. And it's easier, we can inherit itBaseRequestOptions, rewritemerge(...)method.

@Injectable()
export class AuthRequestOptions extends BaseRequestOptions {
 merge(options?: RequestOptionsArgs): RequestOptions {
  let newOptions = (options);
  let token = ();
  (, token);
  return newOptions;
 }
}

thismerge(...)The method will be called every time the request is used to merge the options and default options during the request.

After testing, this method can also perfectly solve our needs.

Summarize

Therefore, this is the power and convenience of Angular. It uses many characteristics of phenomenal objects, such as inheritance, interface, implementation, etc.; it also uses many characteristics of server-side Java frameworks, such as containers, etc. What's said aboveproviderThat is, the provider of object instances in the container, originallyRequestOptionsThe type of provider isBaseRequestOptions, however, I inherited it, rewritten a method, and changed the provider of this type to the class I wrote. In this way, when the Angular container is initialized, it will use the class I provide to create an instance of this type.

Moreover, during the exploration of these implementation methods, I did not check Angular's documentation at all, nor did I check any information online. Knowledge to view the definition of a class or interface, through its annotations, I have an idea, and then try to implement it, and it succeeds. This is also the traversal brought to me by TypeScript.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.