How to extend Angular 2 HTTP Service with Factories

Recently I had an issue where I needed to add a JWT Token to the header of my API calls with Angular2 and with me practicing T-DRY, I had to figure out a way to do it.
I searched and searched and found almost nothing. I did however find a way to build a factory to do it and then noticed that there is hardly any documentation and hardly any resources about it. So I figured it would be helpful to others if I added it here.

Creating the Service.

Below is what I came up with for the HTTP Service in TypeScript.

import {Injectable} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {Router} from '@angular/router';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';

import {
  Http,
  RequestOptions,
  RequestOptionsArgs,
  Response,
  Request,
  Headers,
  XHRBackend
} from '@angular/http';

import {MyCustomRequestOptions} from '../_helpers';

@Injectable()
export class HttpService extends Http {

  public apiUrl = 'api/';

  public constructor(
    backend: XHRBackend,
    defaultOptions: MyCustomRequestOptions,
    private router: Router
  ) {
    super(backend, defaultOptions);
  }

  public get(url: string, options?: RequestOptionsArgs): Observable {
    return super.get(this.getFullUrl(url), this.requestOptions(options))
    .catch(this.onCatch);
  }

  public post(url: string, data: any, options?: RequestOptionsArgs): Observable {
    return super.post(this.getFullUrl(url), data, this.requestOptions(options))
    .catch(this.onCatch);
  }

  public put(url: string, data: any, options?: RequestOptionsArgs): Observable {
    return super.put(this.getFullUrl(url), data, this.requestOptions(options))
    .catch(this.onCatch);
  }

  public delete(url: string, options?: RequestOptionsArgs): Observable {
    return super.delete(this.getFullUrl(url), this.requestOptions(options))
    .catch(this.onCatch);
  }

  public request(url: string | Request, options?: RequestOptionsArgs): Observable {
    return super.request(url, options).catch((error: Response) => {
      if ((error.status === 401 || error.status === 403)) {
        console.log('The authentication session expires or the user is not authorised. Force refresh of the current page.');
        sessionStorage.clear();
        this.router.navigate(['/login']);
      }

      return Observable.throw(error);
    });
  }

  private getFullUrl(url: string): string {
    return this.apiUrl + url;
  }

  private onCatch(error: any, caught: Observable): Observable {
    return Observable.throw(error);
  }

  private requestOptions(options?: RequestOptionsArgs): RequestOptionsArgs {
    if (options == null) {
      options = new MyCustomRequestOptions();
    }
    if (options.headers == null) {
      options.headers = new Headers();
    }

    return options;
  }

}

If you notice I am using a MyCustomRequestOptions. This is where the magic really happens.

Creating Helper MyCustomRequestOptions Class

import {BaseRequestOptions, ResponseContentType} from '@angular/http';
import {User} from '../models/';

export class MyCustomRequestOptions extends BaseRequestOptions {

  public token: string;

  public constructor(customOptions?: any) {
    super();
    this.token = sessionStorage.getItem('token');
    this.responseType = ResponseContentType.Json;
    this.headers.append('Content-Type', 'application/json');
    this.headers.append('Authorization', this.token);
    this.headers.append('X-Requested-With', 'XMLHttpRequest');
  }
}

As you can see I am adding the custom headers that I want on every API call here.
I added the token which I am saving into sessionStorage on login then using it through out the rest of the app to know where there or not you are authorized. I am also using a Auth Service then checks for this and the response from the backend and based on certain headers redirects you back to the login page if you are not authorized.
I added ‘X-Requested-With’, ‘XMLHttpRequest’ header because I since this is a strictly AJAX API I am kicking back anything that is not AJAX.

Creating the Factory

Now we need to create the factory to load our new Service with our custom options

import {XHRBackend} from '@angular/http';
import {Router} from '@angular/router';
import {MyCustomRequestOptions} from '../_helpers';
import {HttpService} from '../services';

function httpServiceFactory(backend: XHRBackend, options: MyCustomRequestOptions, router: Router) {
  return new HttpService(backend, options, router);
}

export {httpServiceFactory};

Now that we have the ground work done it is time to add it to our module, I am using a CoreModule base upon recommend styling per John Papa’s Styling Guide.

Creating the CoreModule

import {NgModule, Optional, SkipSelf} from '@angular/core';
import {CommonModule} from '@angular/common';
import {XHRBackend, RequestOptions} from '@angular/http';
import {Router} from '@angular/router';

// HTTP extention
import {HttpService} from './services';
import {httpServiceFactory} from './_factories';

// Throw error if module tries to load itself
import {throwIfAlreadyLoaded} from './module-import-guard';

@NgModule({
  imports: [
    CommonModule, // we use ngFor
  ],
  exports: [],
  declarations: [],
  providers: [
    {
      provide: HttpService,
      useFactory: httpServiceFactory,
      deps: [XHRBackend, RequestOptions, Router]
    },
  ]
})

export class CoreModule {
  constructor( @Optional() @SkipSelf() parentModule: CoreModule) {
    throwIfAlreadyLoaded(parentModule, 'CoreModule');
  }
}

I want you to this close attention to this bit of code

providers: [
  {
    provide: HttpService,
    useFactory: httpServiceFactory,
    deps: [XHRBackend, RequestOptions, Router]
  },
]

It basically says I want to add HttpService and I want to use httpServiceFactory Factory to created it and I want to pass these parameters XHRBackend, RequestOptions, Router.

In case you were wondering what this…

constructor( @Optional() @SkipSelf() parentModule: CoreModule) {
  throwIfAlreadyLoaded(parentModule, 'CoreModule');
}

bit of code was this is basically checking if the module has been loaded yet and if it is throw an error so that it does not get loaded twice.
For completeness here is the code for that.

Creating throwIfAlreadyLoaded

export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) {
  if (parentModule) {
    throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`);
  }
}

I hope you have found this useful if you find yourself in a position where you need to create a factory in Angular2

Let me know your thoughts, comments, questions & suggestions.