top

Let’s build a Reactive HTTP library for Angular

IntroductionIn this article, we will go over the concept of Reactive Programming, and in the process build a reactive Angular HTTP module like Angular’s built-in Http module.If you have tried your hands on Angular and used its HTTP module, you must have come across a plethora of stuff like Observable, subscribe.Here, you will learn how all this RxJS stuff were incorporated into the Angular Http library.The Angular HTTP module is powered by RxJS. RxJS is a JavaScript library for composing asynchronous and event-based programs by using observable sequences.Loosely speaking, RxJS gives us tools for working with Observables, which emit streams of data.Before we dive into the nuts and bolts of how/what makes Angular HTTP library work, let’s take a look at the core concepts of RxJS.Let’s go over the following topics:Reactive ProgrammingObservableObserverReactive programmingReactive programming is a paradigm for software development that says that entire programs can be built uniquely around the notion of streams.In Angular, we can structure our application to use Observables as the backbone of our data architecture. Using Observables to structure our data is called Reactive Programming. But what are Observables, and Reactive Programming anyway? Reactive Programming is a way to work with asynchronous streams of data.Observables are the main data structure we use to implement Reactive Programming. But I’ll admit, those terms may not be that clarifying. So we’ll look at concrete examples through the rest of this chapter that should be more enlightening.ObservableThis represents a stream of events or data. It got its name from the Observer design pattern. The Observer design pattern is where an object maintains a list of observers and notifying them of any changes to state.It’s easy to implement a basic observer pattern in a few lines:class Observable {    this.subscribers = []    subscribe (subscriber) {        this.subscribers.push(subscriber)    }    notify (msg) {        this.subscribers.forEach((subscriber)=>{            subscriber.next(msg)        })    } }The Observable keeps an array/list of subscribers, and will notify/next each of the subscribers whenever there is a message, i.e when the Observable calls its notify method.// any listener will have the next method const subscriber = {    next: function(msg){        console.log(`I received this message: ${msg}`)    } } let observable = new Observable() observable.add(subscriber) observable.notify('message from the Observable') When the program is run :> I received this message: message from the ObserverThis is a very simple illustration of the observer pattern but it shows how reactive programming basically works.ObserverAn observer is a callback or list of callbacks that listens and knows how to deal with value delivered by the Observable. The put more simply, Observers are consumers of Observable.Observers subscribe to Observable to receive values from it in a sequential manner. the thing there is that the Observer doesn’t request for them.Angular HTTP ModuleIn order to use Angular’s HTTP module, we have to import it like this:import { Http } from '@angular/http'Then, inject it via Dependency Injection:import { Http } from '@angular/http' export class TodoService {    constructor(private http: Http){} }To finally be able to use the Http class, we have to import the HttpModule and add it to the imports array in app.module.ts.// app.module.ts import { HttpModule } from '@angular/http' @NgModule({    imports: [ HttpModule ] }) With this we can perform different CRUDy requests to any resource, using the Http's get, post, delete, and put methods.Accessing a resource through any of this methods returns an Observable, unlike Promises returned by other http libraries (axios etc.):import { Http } from '@angular/http' export class TodoService {    constructor(private http: Http) {}    /** returns list of todos    * [{title: 'Sweep the floor'},{title: 'Wash clothes'}]    **/    getTodoLists (): Observable {        return this.http.get('/api/todos.json')    } }With the Observable we can subscribe to it and receive the values of the request:import { TodoService } from './todoService' @Component({    selector: 'todo',    template: '<div>{todos}</div>' }) export class Todo {    todos: Array<any>    constructor(private todo: TodoService){        this.todo.getTodoLists()            .subscribe({                todos => this.todos = todos,                error => console.log('Error ocurred)            })    }    }Now, we have seen that the result of Http POST, GET methods returns an Observable. With this info, we can design our own HTTP module methods to return an Observable.Source CodeYou can find the source code of this library here.Project SetupTo recap on what we are going to build here. We are going to build an http library for Angular, it can be substituted for Angular's built-in http module and it is going to be reactive.This is an Angular Module, its setup will be different from an Angular app.First, let’s scaffold the project directory ng-http:mkdir ng-http cd ng-httpWe now run the npm init -y command to generate package.json.NB: We could use ng-package to easily scaffold our Angular module, but I chose to do it manually so that we can also learn some tricks, and know how Angular module works.npm init -yThis generates the package.json with our default credentials.Creating project directoriesLet’s create directories that would house our codes depending on their functionality. For now, we need to create src and test directories. Src would contain our sources and test would contain our unit tests:mkdir src mkdir testOur project directory should look like this:ng-http +- src/ +- test/ +- package.json  Installing DependenciesWe are going to need several modules for our project:@angular/compiler@angular/compiler-cli@angular/core@angular/common@angular/platform-browser-dynamiccore-jszone.jsThese are the core modules we will use:{    "dependencies": {        "@angular/common": "^5.0.0",        "@angular/compiler": "^5.0.0",        "@angular/core": "^5.0.0",        "@angular/platform-browser": "^5.0.0",        "@angular/platform-browser-dynamic": "^5.0.0",        "core-js": "^2.4.1",        "karma-typescript": "^3.0.12",        "rxjs": "^5.5.2",        "zone.js": "^0.8.14"    },    "devDependencies": {        "@angular/cli": "1.5.4",        "@angular/compiler-cli": "^5.0.0",        "@types/jasmine": "~2.5.53",        "@types/node": "~6.0.60",        "jasmine-core": "~2.6.2",        "karma": "~1.7.0",        "karma-chrome-launcher": "~2.1.1",        "karma-coverage-istanbul-reporter": "^1.2.1",        "karma-jasmine": "~1.1.0",        "karma-typescript-angular2-transform": "^1.0.2",        "typescript": "~2.4.2"    } } Unit Testing with TestBed, Karma, and JasmineUnit testing is one of the key tenets in Software development. Actually, unit testing is a way of performing any kind of test against every individual unit of a software. Individual units? Well, in most programming languages, it can be a function, a subroutine, method or a property.Unit testing is possible in any language. All it needs, is just to develop a separate program that executes each unit providing input data and asserting the output matches the expected. Ideally, the testing program must be written in the same language you testing against.Testing is such a common activity with JavaScript, especially with the rise of JS frameworks Angular, React, Vue, Ember  ... Most of these frameworks come with the unit and e2e testing incorporated. And so rise of testing frameworks: Enzyme, Jest, Karma, Jasmine, Sinon to name but a few.These testing frameworks and libraries greatly reduce the time it takes to write tests. Angular in particular has its default testing frameworks: Jasmine and Karma.JasmineJasmine is the most popular testing framework in the JS framework world. It supports a software development practice called Behaviour Driven Development or BDD for short. IT’s a subset of Test Driven Development (TDD). Jasmine tries to describe tests in a human-readable form, so that non-tech. people can really understand what is being tested. Jasmine has several functions which come in handy. The general idea to note is that the Jasmine function executes a given test function to check if it produces the same result as the expected result.KarmaKarma is a task runner for tests. It is a test automation utility for managing and controlling the execution of tests. It spawns the browser and runs the tests inside of them.Angular apps are run in a browser environment. We could use any browser Chrome, Firefox, Safari. So, here we practically load our test(s) in any browser to run and display the result on the browser. You know, it gets boring and stressful reloading/refreshing the browser all time to see your test(s) results.In comes Karma to save the day!!! It spawns the browsers and runs the Jasmine tests inside of them and display the result in our terminal. Not only that, it runs idly looking for file change(s) and re-runs the test(s) if any change is caught.TestBedTestBed (or Angular TestBed) is an Angular-specific testing framework that provides Angular behavior to test environment. It supports test(s) written/dependent on the Angular. Jasmine or Karma won’t provide services like Dependency Injection, Change Detection mechanism of Angular, Components or Directives or Pipes interaction with templates, Angular Compilation technique. So, TestBed actually, lets us run test(s) based-on Angular.We are going to use Jasmine and Karma to test our module and classes. Of course!! As stated earlier they are the frameworks/libraries for testing Angular apps/modules. In addition to Jasmine and Karma, we going to employ another framework, karma-typescript.Karma-TypeScriptJasmine is for testing JS files, but Angular apps/modules are written in TS. To test TS files it must be first compiled to JS before Jasmine can run the tests. Karma-TypeScript compiles the TS files for us and passes the compiled files to Jasmine to run them. Like other testing frameworks/libraries, it should be installed as a development dependency.Let’s begin by installing our testing libraries/frameworks. Let’s install Jasmine, Karma, and Karma-TypeScript first:npm i jasmine-core karma karma-typescript -D Let’s add some plugins we will also need:npm i @types/jasmine @types/node karma-chrome-launcher karma-coverage-istanbul-reporter karma-jasmine karma-typescript-angular2-transform typescript -D Next, we create and configure Jasmine, Karma and Karma-TypeScript in a configuration script file karma.conf.js:// karma.conf.js module.exports = function(config) {    config.set({        frameworks: ["jasmine", "karma-typescript"],        files: [            { pattern: "src/**/*.ts" },            { pattern: "test/**/*.ts" }        ],        preprocessors: {            "src/**/*.ts": ["karma-typescript", "coverage"],            "test/**/*.ts": ["karma-typescript"]        },        reporters: ["progress", "coverage", "karma-typescript", "kjhtml"],        client: {            clearContext: false // leave Jasmine Spec Runner output visible in browser        },        coverageIstanbulReporter: {            reports: ['html', 'lcovonly'],            fixWebpackSourcePaths: true        },        karmaTypescriptConfig: {            tsconfig: "./tsconfig.spec.json"        },        browsers: ["Chrome"],        port: 9876,        colors: true,        logLevel: config.LOG_INFO,        autoWatch: true,        singleRun: false    }); };In order to serve our tests files, Karma needs to know about our project and it’s done through a configuration file, karma.conf.js. The configuration file informs Karma testing frameworks to use, file(s) to process, preprocessors to use, the type of browser it should spawn the port on which to serve the tests and so many other useful options.Now we create tsconfig.json:tsc --initThe above command will create a tsconfig.json file in our project's root folder. tsconfig.json is a TypeScript configuration file that tells TypeScript how to compile our project's TS files to JS.Make sure your tsconfig.json looks like this:{    "compilerOptions": {        "target": "es5",        "module": "es2015",        "lib": [            "es2015",            "dom"        ],        "declaration": true,        "sourceMap": true,        "outDir": "dist",        "rootDir": ".",        "strict": true,        "noImplicitAny": true,        "strictNullChecks": true,        "moduleResolution": "node",        "baseUrl": ".",        "paths": {            "@angular/core": ["node_modules/@angular/core"],            "rxjs/*": ["node_modules/rxjs/*"]        },        "inlineSources": true,        "experimentalDecorators": true,        "skipLibCheck": true,        "stripInternal": true    } }We need to create another tsconfig.json for our tests. karma-typescript will need info on how to compile our TS unit tests files. Looking back at our karma.conf.js file, we set a property karmaTypescriptConfig.tsconfig to tsconfig.spec.json. This tells karma-typescript which configuration file to use when compiling our TS files for testing.OK, let’s create tsconfig.spec.json:touch tsconfig.spec.json Make it look like this:{    "compilerOptions": {        "target": "es5",        "module": "commonjs",        "lib": [            "es2015",            "dom"        ],        "declaration": true,        "sourceMap": true,        "outDir": "dist",        "rootDir": ".",        "strict": true,        "noImplicitAny": true,        "strictNullChecks": true,        "moduleResolution": "node",        "baseUrl": ".",        "paths": {            "@angular/core": ["node_modules/@angular/core"],            "rxjs/*": ["node_modules/rxjs/*"]        },        "types": [            "jasmine",            "node"        ],        "files": [            "test/test.ts"        ],        "include": [            "**/*.spec.ts"        ],        "inlineSources": true,        "experimentalDecorators": true,        "skipLibCheck": true,        "stripInternal": true    } } Let’s add an NPM script so that we can conveniently start Karma from the terminal:{    "script": {        "test": "karma start"    } }So now we can run the command npm run test in our terminal, it will invoke the karma start command.Angular module setupSetting up an Angular module library is very easy it’s almost the same as creating a module in an Angular app.When creating an Angular module, there are some points we should note:Never import BrowserModule, it is always imported by the consuming root module (AppModule).Export all public Components, Directives, Pipes in the exports array.Export all public classes (Services) in the providers array.Import the CommonModule when the need to use *NgFor, *NgIf arises.Classes in the providers array are available everywhere in the app.Components/Directives/Pipes declared only in declarations array are only accessible inside your module.With these points in mind, let’s install dependencies we will need to build our Angular library:npm i @angular/common @angular/compiler @angular/core @angular/cli @angular/compiler-cli rxjs -D These are core Angular libraries. We need for sure because we are developing a library for the Angular framework. Let’s go through what just installed:@angular/common: This holds common directives that are used in Angular apps (*NgIf, *NgFor, *NgSwitch, *NgClass, *NgStyle).@angular/compiler: This is a great piece of engineering. It compiles our components/directives etc into JS factories.@angular/core: This is the citadel of Angular. It is responsible for bootstrapping our app and loading our templates/components/directives/factories(Component, Directive and NgModule etc) into the browser DOM.@angular/cli: This library exposes the ng command to users. It provides several command-line utilities:build: This compiles and builds Angular apps for deployment.serve: This builds and serves Angular apps over localhost:4200.test: This sub-command runs units and end-to-end testing in Angular apps.@angular/compiler-cli: This compiles our files in an Angular specific way. It is invoked using the ngc command.rxjs: Angular makes heavy use of RxJS. Most of its parts are powered by RxJS. RxJS is a JS library that brings Reactive programming to JS.We are done installing our core libraries. Now, we begin implementing our Http module. We will use TDD approach here. We first write a failing test then, augment the production code to make the test pass.Our Http module will implement the same concepts of the Angular Http module.We will use the notion of backends to build our module. In a browser environment, XMLHttpRequest is used to query resources over a network. Different environments have different methods/implementation of querying resources. In a Node.js environment XMLHttpRequest cannot be used and it does not exist there. Also, during tests querying resources should be done using mocks, in this case, maybe a backend should be mock-ed.So different environments use different backend implementation:Browser uses BrowserBackendNode.js uses NodeBackendMock uses MockBackendWe are just trying to build our Http library in such a way users can plugin their own backend implementation if needed.import {Http, MyNodeBackend, HTTP_PROVIDERS, BaseRequestOptions} from '@angular/http'; @Component({    viewProviders: [        HTTP_PROVIDERS,        {provide: Http, useFactory: (backend, options) => {        return new Http(backend, options);        }, deps: [MyNodeBackend, BaseRequestOptions]}] }) class MyComponent {    constructor(http: Http) {        http.request('people.json').subscribe(res => this.people = res.json());    } } We will build the default browser backend because it will be used in an Angular app which will, of course, run in a browser environment.We will first implement our XMLHttpRequest backend. Let's create a folder backend/ inside our src/ folder. Next, create backend/xhr_backend.ts.We are going to start our karma daemon, as we are using a TDD methodology. We also need to put a base test that initializes our Angular TestBed environment.Create test/base.spec.ts and add the following contents inside:// test/base.spec.ts import "core-js" import "zone.js/dist/zone"; import "zone.js/dist/long-stack-trace-zone"; import "zone.js/dist/proxy"; import "zone.js/dist/sync-test"; import "zone.js/dist/jasmine-patch"; import "zone.js/dist/async-test"; import "zone.js/dist/fake-async-test"; import { TestBed,getTestBed } from "@angular/core/testing"; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from "@angular/platform-browser-dynamic/testing"; TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); We imported core-js, zone.js, then also imported TestBed, BrowserDynamicTestingModule, platformBrowserDynamicTesting from Angular. These make it possible to test Angular-dependent code.In your terminal, run the test command:npm run test This command will go through a series of steps invoking its dependencies, launching the Chrome browser, compiling all tests file in test/ folder and finally, displaying the tests results on Chrome and on the terminal. Also, it(karma) will sit idly watching for file changes and will re-run the tests whenever a file change occurs.Implementing Browser BackendAs we stated earlier, our module will use the notion of the backend to know how to access a resource depending on the environment it’s running. Browser-based HTTP calls cannot be used in a Node.js environment. Also, during tests HTTP calls are mocked, so a backend should be developed for tests only. This means, you plugin the backend for the environment.We will be developing the backend for a browser in this article. You can practicalize what you have learned here by adding different backends for different environments.In the implementation of our backend, we will be creating three classes: XHRBackend, XHRConnection, and XHRBrowser.XHRBackend will be responsible for creating and returning a connection. XHRConnection completes the request on instantiation. XHRBrowser will create and return the XMLHttpRequest object.XHRBrowserXHRBrowser class will contain a method build that will return an instance of XMLHttpRequest. Let's make our first test case to implement this behavior. create test/xhrbrowser.spec.ts file and add this code:import {TestBed, inject} from '@angular/core/testing'; import { XHRBrowser } from '../src/backend/xhr_backend'; describe('XHRBrowser', () => {  beforeEach(() => {    TestBed.configureTestingModule({      providers: [        XHRBrowser,      ]    });  });  it('`xhr.build()` should return an instance of XMLHttpRequest', inject([XHRBrowser], (xhr: XHRBrowser) => {    expect(xhr.build() instanceof XMLHttpRequest).toBe(true);  })); });Here, we imported TestBed, inject and non-existentent XHRBrowser. Next, we defined our first test suite using the describe function. In the beforeEach function, we configured a testing module using the TestBed class. This creates a testing Angular module that can be used to provide Directives, Components, Providers and so on.We provided our XHR Browser class in the providers array. With this we can get the instance of XHR Browser using Injector or inject. We defined a test spec using the it function, see how inject was used to get the instance of XHR Browser and pass it to the function argument.At last, we defined our test case, calling the build() method of the XHRBrowser instance, xhr. We expect it to return the instance of XMLHttpRequest.NB: This test will fail, this is exactly what we want because an important step in test-driven development is seeing the test fail first.To make this test pass, we are going to define XHRBrowser class in src/backend/xhrbackend.ts:// src/backend/xhrbackend.ts import {Injectable} from '@angular/core' @Injectable() export class XHRBrowser { } Let’s create a method build to return an instance of XMLHttpRequest:// src/backend/xhrbackend.ts import {Injectable} from '@angular/core' @Injectable() export class XHRBrowser {    build() {        return new XMLHttpRequest()    } }That’s it, we created an instance of XMLHttpRequest using the new keyword and returned it using the return keyword. Now, our test passes.XMLHttpRequest is a built-in class in browser environment used to access resources over a network. In other hands, it used to connect your app to the internet.XHRConnectionThis creates connections using XMLHttpRequest. When given a request and an XHR Browser instance, it initializes an Observable with an observer that creates XMLHttpRequest object, assign events to the object and sends the request. The Observable instance is assigned to property response with type Observable. This response property is what is returned when a connection is created. This is the reason, users have to use subscribe function to get their data:this.http.get('books.json').subscribe(res => this.books = res.json()) The data is next-ed into the data stream by XHRConnection and returning the data stream, response of type Observable.Let’s begin the implementation, first create test/xhrconnection.spec.ts. Before we add any test case, we are going to create a mock of XHR Browser. Why? XHRConnection uses the build method XHRBrowser provides to create XMLHttpRequest object, and we wouldn't want to access a real network during tests and maybe the servers may be down. What we need to do is to go without real HTTP communication and fake the servers. What we are going to do is to mock XMLHttpRequest, yes, I said XHRBrowser before. We will extend XHRBrowser functionality to override the build method so that we can return our own mock XMLHttpRequest.Now, we define two classes MockXMLhttpRequest and MockXHRBrowser:// test/xhrconnection.spec.ts let abortSpy: any; let sendSpy: any; let openSpy: any; class MockXMLHttpRequest  {    response: any    status: number  constructor() {    abortSpy = spyOn(this,'abort');    sendSpy = spyOn(this,'send');    openSpy = spyOn(this,'open');  }  abort() {}  send () {}  open() {}  setResponse(response: any) { this.response = response}  setStatus(status:number) { this.status = status}  dispatchEvent(type: string) {    (this as any)[type]()  } } class MockXHRBrowser extends XHRBrowser {    mockXMLHttpRequest:MockXMLHttpRequest    build() {        this.mockXMLHttpRequest = new MockXMLHttpRequest()        return this.mockXMLHttpRequest    } } In MockXMLHttpRequest class, you see we defined the core methods found in XMLHttpRequest open, send and abort. We used spyOn to mock the functions so that we can know whether the function is called, the return value and the arguments. Also, there several setters that we can use to manipulate different properties that XMLHttpRequest provides. Last we have a dispatchEvent method, this will be used to mimic different XMLHttpRequest events like onload, onerror, ontimeout. So you see with this we can simulate different scenarios and test against them.Looking at the second class we defined, MockXHRBrowser. We overrode the build method so that we can return our MockXMLHttpRequest instance.OK, now we are done with creating mock classes. Let’s begin implementing XHRConnection class.As we said before, XHRConnection creates an Observable instance and assign it to a response property.Good, let’s add a test case that asserts an XHRConnection instance was created. Remember, XHRConnection should take in a request object and XHRBrower instance in its constructor.// test/xhrconnection.spec.ts import { XHRConnection, XHRBrowser } from '../src/backend/xhr_backend'; ... describe('XHRConnection', () => {  it('should be created', () => {    expect(new XHRConnection({}, new MockXHRBrowser())).toBeTruthy();  }); }) To make this pass we define XHRConnection in src/backend/xhr_backend.ts:// src/backend/xhr_backend.ts ... export class XHRConnection {    response: Observable<any>    constructor(requestOptions: any, browserXHR: XHRBrowser) { } }Here, we created XHRConnection class with a constructor that takes in a request object and a XHRBrowser instance. Also, we defined a response property.We need to instantiate an Observable and assign it to response. Let’s add a test case that checks for this:// test/xhrconnection.spec.ts ... import { Observable } from 'rxjs'; ... describe('XHRConnection', () => { ...  it('`response` should be instance of Observable', () => {      const conn = new XHRConnection({}, new MockXHRBrowser())    expect(conn.response instanceof Observable).toBeTruthy();  }); })We assigned XHRConnection instance to conn, and used instanceof keyword to test conn.response is an instance of Observable.Now, in XHRConnection class, we initialize an Observable instance and assign it to the response property:// src/backend/xhr_backend.ts ... export class XHRConnection {    response: Observable<any>    constructor(requestOptions: any, browserXHR: XHRBrowser) {        this.response = new Observable(function (observer: any) { })    } }The function arg will be called with an observer function whenever the response is subscribed to. The observer is where we will yield the response of the Ajax call. With these our test case passes.Now, to make Ajax calls with an Observable, we will wrap XMLHttpRequest inside the function argument. First, we create the XMLHttpRequest instance and call the open() method with our request method and the URL as arguments. Event listeners are registered to capture events XMLHttpRequestemits, then the request is sent using the send() method.Examplelet xHR = new XMLHttpRequest() xHR.open('POST', 'https://google.com?search=rxjs') xHR.onload = function() { this.rxjs = xHR.response } xHR.send()Above is a typical example of how to make a simple Ajax request. We constructed an XMLHttpRequest object, then, opened a connection, telling it the type of request (GET, POST, DELETE, PUT or OPTIONS) we want to make and the resource URL. An onload event was registered, it assigns the response of the call to rxjs variable. The onload event is emitted when the Ajax call is successful. Next, we initiate the call using the send method.As we have seen, the onload event is where we get our response. So, in our own case, we will yield the response to the observer function and complete the sequence.Let’s instantiate XMLHttpRequest object:// src/backend/xhr_backend.ts ... export class XHRConnection {    response: Observable<any>    constructor(requestOptions: any, browserXHR: XHRBrowser) {        this.response = new Observable(function (observer: any) {            let httpRequest = browserXHR.build();        })    } }We assigned the XMLHttpRequest object to httpRequest. Next, we open a connection using the open() method. Let's add a test case that checks the open method is called when we subscribe to the Observable.// test/xhrconnection.ts ... describe('XHRConnection', () => { ...  it('`open` should be called', () => {    const conn = new XHRConnection({}, new MockXHRBrowser())    conn.response.subscribe()    expect(openSpy).toHaveBeenCalled()  }); })Note, the function arg in the Observable is only called when at laest on Observer subcribes to it. So here, instantiated XHRconnection and subcribed to the response Observable. We expect the spy function openSpy to be called. To make this pass, we simply call the open function in our XHRConnection class:// src/backend/xhr_backend.ts ... export class XHRConnection {    response: Observable<any>    constructor(requestOptions: any, browserXHR: XHRBrowser) {        this.response = new Observable(function (observer: any) {            let httpRequest = browserXHR.build();           httpRequest.open(requestOptions.method, requestOptions.url);        })    } } We register our listeners, we are going to register the onload listener. The onload event is called whenever there is a successful Ajax call. So we hook into it to get the response.Let’s add the onload listener, first we add a case that asserts that onload function exists when the subscribe function is called:// test/xhrconnection.spec.ts ... describe('XHRConnection', () => { ...  it('onload listener should exist on subscribe call', () => {      const mockXHRBrowser = new MockXHRBrowser()      const conn = new XHRConnection({}, mockXHRBrowser)      conn.response.subscribe()     expect((mockXHRBrowser.mockXMLHttpRequest as any)['onload']).toBeTruthy()  }); });Making it pass we do this:// src/backend/xhr_backend.ts ... export class XHRConnection {    response: Observable<any>    constructor(requestOptions: any, browserXHR: XHRBrowser) {        this.response = new Observable(function (observer: any) {            let httpRequest = browserXHR.build();           httpRequest.open(requestOptions.method, requestOptions.url);           httpRequest.onload = function() {           }        })    } }OK, we registered for the onload event. Now, we going to flesh out the function. Here, we receive the result of the Ajax/HTTP call in a property response and yield it to the observer using next method. In reactive programming, next is used to put a value to a data stream so that its observers can get the data in its next function.// src/backend/xhr_backend.ts ... export class XHRConnection {    response: Observable<any>    constructor(requestOptions: any, browserXHR: XHRBrowser) {        this.response = new Observable(function (observer: any) {            let httpRequest = browserXHR.build();           httpRequest.open(requestOptions.method, requestOptions.url);           httpRequest.onload = function() {                observer.next(httpRequest.response)                observer.complete()           }        })    } }We supply the response to the observer via its next function and we tell the observer that’s the end of the data stream by calling its complete function.We are done with our onload event function. Like we said earlier, for XMLHttpRequest to send the request over a network, we need to call the sendmethod.Let’s add a test that asserts that the send method is called when the response Observable is subscribed to:// test/xhrconnection.spec.ts ... describe('XHRConnection', () => { ...  it('`send` should be called', () => {    const conn = new XHRConnection({}, new MockXHRBrowser())    conn.response.subscribe()    expect(sendSpy).toHaveBeenCalled()  }); }) Simple, we call the send method:// src/backend/xhr_backend.ts ... export class XHRConnection {    response: Observable<any>    constructor(requestOptions: any, browserXHR: XHRBrowser) {        this.response = new Observable(function (observer: any) {            let httpRequest = browserXHR.build();           httpRequest.open(requestOptions.method, requestOptions.url);           httpRequest.onload = function() {                observer.next(httpRequest.response)                observer.complete()           }           httpRequest.send()        })    } }We are done with our XHRConnection. To recap on what we did here. We created a constructor that takes in a request object and XHRBrowser instance, then, we instantiated an Observable with a function arg that creates and send an HTTP call using XMLHttpRequest, the result is fed to an observer. The instance of the Observable is assigned to a property response. This response is what will be returned to the users so that they can subscribe to it and get their data.XHRBackendXHRBackend returns an instance of XHRConnection. It typically creates the connection to the backend. It doesn’t have to know the type of connection, it’s left for the XHRConnection class to provide the kinda connection that is ideal for the current environment.To begin, let’s create an XHRBackend class in src/backend/xhr_backend.ts:// src/backend/xhr_backend.ts ... @Injectable() export class XHRBackend { } Here, we just defined our XHRBackend class, we also annotated it with the Injectable decorator, this tells Angular to mark this class for Dependency Injection.XHRBackend will take the instance of XHRBrowser in its constructor, so we can feed it to XHRConnection when we are creating a connection. Passing an instance of a class is one of the best practices in programming so that our code would be easy to test and maintain.As always we write a failing test first before that create this test/xhrbackend.spec.ts file. Then, add this test case:// test/xhrbackend.spec.ts import {TestBed, inject} from '@angular/core/testing'; import { XHRBackend, XHRConnection, XHRBrowser } from '../src/backend/xhr_backend'; import {MockXHRBrowser} from './xhrconnection.spec' describe('XHRBackend', () => { beforeEach(() => {    TestBed.configureTestingModule({      providers: [        { provide: XHRBrowser, useClass: MockXHRBrowser },        {            provide:XHRBackend,            useFactory: (xHRBRowser:XHRBrowser) => {                return new XHRBackend(xHRBRowser)            },            deps:[XHRBrowser]        }      ]    }); }); it('should be created', inject([XHRBackend], (xhr: XHRBackend) => {    expect(xhr).toBeTruthy();  })); })We imported classes and functions we will be using. Then, we configured our Testing module to provide our classes. See, we used the useClass property to tell Angular to provide our MockXHRBrowser as XHRBrowser. Next, we declared a test spec that checks XHRBackend instance is created.Let’s configure our XHRBackend constructor to accept an XHRBrowser instance:// src/backend/xhr_backend.ts ... @Injectable() export class XHRBackend {    constructor(private browserXHR: XHRBrowser) {  } }Next, we will create a method createConnection this method will take an object as arg and create an instance of XHRConnection.Let’s add a test case that checks createConnection returns an instance of XHRConnection:// test/xhrbackend.spec.ts ... describe('XHRBackend', () => { ... it('should return XHRConnection instance', inject([XHRBackend], (xhr: XHRBackend) => {    expect(()=> xhr.createConnection({})).not.toThrow();    expect(xhr.createConnection({}) instanceof XHRConnection).toBeTruthy();  })); })You see here, we made sure the createConnection method exists and doesn’t throw. Also, we checked the return value of createConnection is an instance of XHRConnection.To make this test pass, we simply define the createConnection method:// src/backend/xhr_backend.ts ... @Injectable() export class XHRBackend {    constructor(private browserXHR: XHRBrowser) {  }    createConnection(requestOptions: any) {    } }Next, we make it return an instance of XHRConnection:// src/backend/xhr_backend.ts ... @Injectable() export class XHRBackend {    constructor(private browserXHR: XHRBrowser) {  }    createConnection(requestOptions: any) {        return new XHRConnection(requestOptions,this.browserXHR)    } }Wow!!!, we are doing great. Now, our XHRBackend implementation is complete. To recap what we did here, we created XHRBackend class, with a method createConnection that returns an XHRconnection instance. This class would not be used by users but it could be overriden to provide an implementation for a different environment.Implementing Http classFinally, we are to implement the class that will be used by users. This class will be used by end-users to make different types of HTTP calls like GET, POST, DELETE or PUT.Each HTTP method will be exposed to them as methods in the Http class.To begin implementing our Http class, we first create src/Http.ts file and define the Http class in it:// src/Http.ts export class Http { } Http has to be initialized with an instance of XHRBackend. Let’s create test/http.spec.ts file and add the following test case in it:// test/http.spec.ts import {TestBed,  inject} from '@angular/core/testing'; import { Http } from './../src'; import { XHRBackend, XHRBrowser } from '../src/backend/xhr_backend'; import {MockXHRBrowser} from './xhrconnection.spec' describe('Http', () => {  beforeEach(() => {    TestBed.configureTestingModule({      providers: [        {          provide: Http,          useFactory: (xhr: XHRBackend) => {            return new Http(xhr)          },          deps:[XHRBackend]        },        {provide: XHRBrowser, useClass: MockXHRBrowser},        {provide:XHRBackend, useFactory: (x:XHRBrowser)=>{          return new XHRBackend(x)        }, deps:[XHRBrowser]}      ]    });  });  it('should be created', inject([Http], (http: Http) => {    expect(http).toBeTruthy();  })); });As before we imported classes and functions that will be needed, then, configured a Testing module. We add a spec that asserts an Http instance was created.To make this pass, we add a constructor to Http that takes in an XHRBackend instance.// src/Http.ts import { XHRBackend } from './backend/xhr_backend'; export class Http {    constructor(private backend: XHRBackend) {} } Now, we said earlier that we define methods in the Http class with the name as the HTTP Verb they request.For example, get method in our Http class will request a GET method.this.http.get('fishes.json') post method will deliver an Ajax call with POST method.this.http.post('localhost:5000/books', { books: ['art of war','inferno'] }) Also, we need to define a method request that delivers any type of HTTP call. Typically, get, post etc methods will call request method with its own specific HTTP method. get will call request with HTTP method set to GET, post with POST and so on. request method will create an XHRConnection with request URL and METHOD, then return the response Observable.Enough of talk, let’s make it happen. Let’s add test case that checks request method returns an Observable:// test/http.spec.ts describe('Http', ()=> { ...  it('request should return an Observable', inject([Http], (http: Http) => {    http.request({}).subscribe()    expect(http.request({}) instanceof Observable).toBeTruthy();  }) })Making it pass, we define request method in our Http class and make it return the response property from an XHRConnection instance:// src/Http.ts ... import { Observable } from 'rxjs'; ... export class Http {    constructor(private backend: XHRBackend) {}    request(requestOptions: any): Observable<any> {        return this.backend.createConnection(requestOptions).response    } }We used the backend object to create a connection using createConnection method we defined earlier in XHRBackend. The method returns an instance of XHRConnection, so we access the response property and return it. Now, we define our HTTP METHOD specific methods, get, post etc. We are only going to implement get method. post, delete etc methods should be implemented by the reader. You know, practicalizing what you have learned is a sure way of thoroughly understanding a concept. OK, let’s write a test case that asserts that get method exist and is a function: \ // test/http.spec.ts describe('Http', ()=> { ...   it('checks get method exist on Http instance', inject([Http], (http: Http) => {     expect(typeof http.get).toBe('function');   })); }); To make this pass we define get method on Http: // src/Http.ts ... export class Http {     constructor(private backend: XHRBackend) {}     request(requestOptions: any): Observable<any> {         return this.backend.createConnection(requestOptions).response     }     get (url: string, options?: any) {  } } To make sure it returns an Observable, let’s add this test:// test/http.spec.ts describe('Http', ()=> { ...   it('get() call should return an Observable', inject([Http, XHRBrowser], (http: Http,xhrB:XHRBrowser) => {     expect(http.get('people.json') instanceof Observable).toBeTruthy();   })); }); To make it pass, we call request method in get method body, passing in objects with properties set to GET and URL: // src/Http.ts ... export class Http {     constructor(private backend: XHRBackend) {}     request(requestOptions: any): Observable<any> {         return this.backend.createConnection(requestOptions).response     }     get (url: string, options?: any) {         return this.request({ url, method: 'GET' })     } } OK, our Http class implementation is now complete. Users can now use our Http class to query resources over network using the request and get methods.Implementing HttpModuleIn order to use our classes in an Angular environment, we will create a NgModule and provide all our classes in the providers array. This is done so that all our classes would be available app-wide i.e. Any of our classes could be imported and used anywhere on the app.To begin, let’s create src/HttpModule.ts file and add the following:// src/HttpModule.ts import { NgModule } from '@angular/core' import { Observable } from 'rxjs'; import { Http } from './Http' import { XHRBackend, XHRBrowser } from './backend/xhr_backend'; function httpFactory(Xhrbackend: XHRBackend) {     return new Http(Xhrbackend) } @NgModule({     imports: [],     declarations: [],     exports: [],     providers: [         {             provide:Http,             useFactory: httpFactory,             deps: [XHRBackend]         },         XHRBrowser,         XHRBackend     ] }) export class HttpModule {} We define the HttpModule class and annotated it with a NgModule decorator. Most of the properties in the NgModule decorator function are empty expect the providers key, that’s because our module consisted only of plain classes. Passing the classes in the providers array tells Angular to make them available throughout the app.Exporting the public APIWe will create src/index.ts file to export all of our public API. It will contain our NgModule and all classes we want to be used by our users. // src/index.ts export { Http } from './Http'; export { HttpModule } from './HttpModule'; Publishing our libraryBefore we publish our library we have to bundle it. To bundle our library, we need to install some tools:rollup: For bundling all our files into a single file.uglify-js: For minifying our code.npm i rollup uglify-js -D Create src/rollup.config.js file. This tells Rollup how to bundle our code files. Add the following code to it: // src/rollup.config.js export default {     input: 'dist/src/index.js',     output: {         sourceMap: false,         dir: 'dist/bundles/',         file: 'dist/bundles/ng.http.umd.js',         format: 'umd',         name: 'ng.http',         globals: {             '@angular/core': 'ng.core',             'rxjs/Observable': 'Rx',             'rxjs/ReplaySubject': 'Rx',             'rxjs/add/operator/map': 'Rx.Observable.prototype',             'rxjs/add/operator/mergeMap': 'Rx.Observable.prototype',             'rxjs/add/observable/fromEvent': 'Rx.Observable',             'rxjs/add/observable/of': 'Rx.Observable'         }     } }input: The entry point of the bundling process.output: Info on how to spit out the bundled files.output.sourceMap: declares whether to generate sourcemap files for debugging purposes.output.dir: Folder to store the bundled fileoutput.file: Name of the bundled file.output.format: Angular uses the UMD format to deliver its modules, so ours should be umd.output.globals: This tells Rollup which dependencies not to include in the bundling process and set it as a global. This means that the user app of this library will already have the dependencies. There will be some complexities and errors, if the library installs the already-present libraries.We will add some npm scripts to automate the bundling process of our files.We have to transpile our src files in an Angular way using ngc command, then, we bundle the transpiled files and lastly, minify the bundled file.Add these to our package.json:// src/package.json ...     "main": "dist/bundles/ng.http.umd.min.js",     "module": "dist/index.js",     "typings": "dist/index.d.ts",     "scripts": {         "test": "karma start",         "transpile": "ngc",         "package": "rollup -c",         "minify": "uglifyjs dist/bundles/ng.http.umd.js --screw-ie8 --compress --mangle --comments --output dist/bundles/ng.http.umd.min.js",         "build": "npm run transpile && npm run package && npm run minify"     }, ...We have transpile script for running the Angular compiler against our files, package script for bundling the files using Rollup, minify script for minifing our bundled file in dist/bundles/ng.http.umd.js using uglify-js. The build script runs each of them synchronously to achieve a perfect build, dist/bundles/ng.http.umd.min.js. The main property points to dist/bundles/ng.http.umd.min.js, the final output because this is the file our users will be referring to when consuming our module. To see all of this workout, run the command: npm run build Voila!! Our final bundle should reside in dist/bundles/ng.http.umd.min.js now. We need to remove some redundant files that shouldn’t be published alongside our module folder, dist. To do this we add an .npmignore file and put down the name of files and folder we want NPM to ignore: // src/.npmignore .*.swp ._* .git .hg .DS_Store .npmrc .svn .lock-wscript .wafpickle-* config.gypi CVS npm-debug.log src/ Run this command to publish our module:npm publishNB: Make sure you give your project a name via the name key in package.json. So that users can install your library via its name from the npmjs registry. Mine is ng.http.lib, so it can be installed like this npm i ng.http.lib -S. Running an Example appNow we have published our module, let’s see how it works in a small Angular app. We scaffold a barebones Angular and pull in our module. We will use our module instead of Angular’s built-in Http module.ng new test-app --minimal We scaffold a new Angular project. The minimal flag tells ng to create for us an Angular project without any test files and all CSS, HTML should be inline. Next, we pull in our module:npm i ng.http.lib<THE_NAME_OF_YOUR_MODULE> -S We import HttpModule from our library and provide it in the imports array of AppModule: // test-app/src/app/app.module.ts ... import { HttpModule } from 'ng.http.lib'; @NgModule({   declarations: [     AppComponent,   ],   imports: [     BrowserModule,     HttpModule   ],   providers: [],   bootstrap: [AppComponent] }) export class AppModule { } Now, we can use the Http class in any Component/Class. We would pass our Http class into AppComponent constructor, then implement the OnInit interface. We will define ngOnInit method so that it will be called after our component instantiation.Inside the ngOnInit method, we will call the get method to make an HTTP call with our module. It will fetch a message that we will assign to the title property.First, let’s create a server.js file where our get method would query for data.// test-app/server.js const http = require('http') http.createServer(function(req, res) {         res.setHeader('Access-Control-Allow-Origin', '*')         res.setHeader('Access-Control-Allow-Methods', ['GET', 'POST'])         res.end("Message from Server")     })     .listen(3000, () => {         console.log(`Server listening on port: 3000`)     }) We required the http module, then created a server using createServer() function and made it listen on port 3000. On access it returns the message Message from Server. We are done with the server. Let’s implement ngOnInit method: // test-app/src/app/app.component.ts import { Component, OnInit } from '@angular/core'; import { Http } from 'ng.http.lib'; @Component({   selector: 'app-root',   template: `     <div style="text-align:center">       <h1>         Welcome to {{title}}!       </h1>     </div>       `,   styles: [] }) export class AppComponent implements OnInit {   title = 'app';   constructor(private http: Http){}   ngOnInit() {     this.http.get('http://localhost:3000').subscribe((res:any)=>{       this.title = res     })   } } We used the get method to query a network, then subscribed to the stream to receive data. Then, we update the property title with the data received. To see it in action. First, run the server.js file:node server.jsThen, on another terminal, in the root directory of the project, start the ng server:ng serveOpen your browser and navigate to localhost:4200. You will see "Welcome to Message from Server" displayed on your browser. With our own custom HTTP module, we successfully accessed and fetched data over network !!! We can now substitute our module for Angular’s built-in HTTP module. ConclusionWe learned a lot of things during the course of this article:Reactive programming.Observables and Observers.Angular modules and how to set up a new Angular module.Testing frameworks: Jasmine, Sinon, Karma.How to setup up tests for Angular projects.How to build apps using TDD approach.How to test Angular modules/project using TestBed.I know we violated a lot of best practices and some things that should have been done in a more clever way, the most important thing is that we learned how to build a reactive http library for Angular and also, some concepts about modern web app development.Like I said earlier you can expand the application to add more functionality, I’ll be glad to hear your stories and feedback. Thanks !!!
Rated 4.5/5 based on 13 customer reviews
Normal Mode Dark Mode

Let’s build a Reactive HTTP library for Angular

Chidume Nnamdi
Blog
24th Jul, 2018
Let’s build a Reactive HTTP library for Angular

Introduction

In this article, we will go over the concept of Reactive Programming, and in the process build a reactive Angular HTTP module like Angular’s built-in Http module.

If you have tried your hands on Angular and used its HTTP module, you must have come across a plethora of stuff like Observable, subscribe.

Let’s build a Reactive HTTP library for Angular

Here, you will learn how all this RxJS stuff were incorporated into the Angular Http library.

The Angular HTTP module is powered by RxJS. RxJS is a JavaScript library for composing asynchronous and event-based programs by using observable sequences.

Let’s build a Reactive HTTP library for Angular

Loosely speaking, RxJS gives us tools for working with Observables, which emit streams of data.

Before we dive into the nuts and bolts of how/what makes Angular HTTP library work, let’s take a look at the core concepts of RxJS.

Let’s go over the following topics:

  • Reactive Programming

  • Observable

  • Observer


Reactive programming

Reactive programming is a paradigm for software development that says that entire programs can be built uniquely around the notion of streams.

In Angular, we can structure our application to use Observables as the backbone of our data architecture. Using Observables to structure our data is called Reactive Programming. But what are Observables, and Reactive Programming anyway? Reactive Programming is a way to work with asynchronous streams of data.

Observables are the main data structure we use to implement Reactive Programming. But I’ll admit, those terms may not be that clarifying. So we’ll look at concrete examples through the rest of this chapter that should be more enlightening.


Observable

This represents a stream of events or data. It got its name from the Observer design pattern. The Observer design pattern is where an object maintains a list of observers and notifying them of any changes to state.

It’s easy to implement a basic observer pattern in a few lines:

class Observable {
   this.subscribers = []
   subscribe (subscriber) {
       this.subscribers.push(subscriber)
   }
   notify (msg) {
       this.subscribers.forEach((subscriber)=>{
           subscriber.next(msg)
       })
   }
}


The Observable keeps an array/list of subscribers, and will notify/next each of the subscribers whenever there is a message, i.e when the Observable calls its notify method.

// any listener will have the next method
const subscriber = {
   next: function(msg){
       console.log(`I received this message: ${msg}`)
   }
}
let observable = new Observable()
observable.add(subscriber)
observable.notify('message from the Observable')


When the program is run :

> I received this message: message from the Observer

This is a very simple illustration of the observer pattern but it shows how reactive programming basically works.

observabal sequence


Observer

An observer is a callback or list of callbacks that listens and knows how to deal with value delivered by the Observable. The put more simply, Observers are consumers of Observable.

observers are the listeners

Observers subscribe to Observable to receive values from it in a sequential manner. the thing there is that the Observer doesn’t request for them.


Angular HTTP Module

In order to use Angular’s HTTP module, we have to import it like this:

import { Http } from '@angular/http'


Then, inject it via Dependency Injection:

import { Http } from '@angular/http'
export class TodoService {
   constructor(private http: Http){}
}


To finally be able to use the Http class, we have to import the HttpModule and add it to the imports array in 

app.module.ts.
// app.module.ts
import { HttpModule } from '@angular/http'
@NgModule({
   imports: [ HttpModule ]
})


With this we can perform different CRUDy requests to any resource, using the Http's get, post, delete, and put methods.

Accessing a resource through any of this methods returns an Observable, unlike Promises returned by other http libraries (axios etc.):

import { Http } from '@angular/http'
export class TodoService {
   constructor(private http: Http) {}
   /** returns list of todos
   * [{title: 'Sweep the floor'},{title: 'Wash clothes'}]
   **/
   getTodoLists (): Observable {
       return this.http.get('/api/todos.json')
   }
}


With the Observable we can subscribe to it and receive the values of the request:

import { TodoService } from './todoService'
@Component({
   selector: 'todo',
   template: '<div>{todos}</div>'
})
export class Todo {
   todos: Array<any>
   constructor(private todo: TodoService){
       this.todo.getTodoLists()
           .subscribe({
               todos => this.todos = todos,
               error => console.log('Error ocurred)
           })
   }   
}


Now, we have seen that the result of Http POST, GET methods returns an Observable. With this info, we can design our own HTTP module methods to return an Observable.


Source Code

You can find the source code of this library here.

Project Setup

To recap on what we are going to build here. We are going to build an http library for Angular, it can be substituted for Angular's built-in http module and it is going to be reactive.

This is an Angular Module, its setup will be different from an Angular app.

First, let’s scaffold the project directory ng-http:

mkdir ng-http
cd ng-http


We now run the npm init -y command to generate package.json.

NB: We could use ng-package to easily scaffold our Angular module, but I chose to do it manually so that we can also learn some tricks, and know how Angular module works.

npm init -y

This generates the package.json with our default credentials.

Creating project directories

Let’s create directories that would house our codes depending on their functionality. For now, we need to create src and test directories. Src would contain our sources and test would contain our unit tests:

mkdir src
mkdir test


Our project directory should look like this:

ng-http
+- src/
+- test/
+- package.json


 Installing Dependencies

We are going to need several modules for our project:

  • @angular/compiler

  • @angular/compiler-cli

  • @angular/core

  • @angular/common

  • @angular/platform-browser-dynamic

  • core-js

  • zone.js

These are the core modules we will use:

{
   "dependencies": {
       "@angular/common": "^5.0.0",
       "@angular/compiler": "^5.0.0",
       "@angular/core": "^5.0.0",
       "@angular/platform-browser": "^5.0.0",
       "@angular/platform-browser-dynamic": "^5.0.0",
       "core-js": "^2.4.1",
       "karma-typescript": "^3.0.12",
       "rxjs": "^5.5.2",
       "zone.js": "^0.8.14"
   },
   "devDependencies": {
       "@angular/cli": "1.5.4",
       "@angular/compiler-cli": "^5.0.0",
       "@types/jasmine": "~2.5.53",
       "@types/node": "~6.0.60",
       "jasmine-core": "~2.6.2",
       "karma": "~1.7.0",
       "karma-chrome-launcher": "~2.1.1",
       "karma-coverage-istanbul-reporter": "^1.2.1",
       "karma-jasmine": "~1.1.0",
       "karma-typescript-angular2-transform": "^1.0.2",
       "typescript": "~2.4.2"
   }
}

 Unit Testing with TestBed, Karma, and Jasmine


bugs and unavoidable


Unit testing is one of the key tenets in Software development. Actually, unit testing is a way of performing any kind of test against every individual unit of a software. Individual units? Well, in most programming languages, it can be a function, a subroutine, method or a property.

unit testing


Unit testing is possible in any language. All it needs, is just to develop a separate program that executes each unit providing input data and asserting the output matches the expected. Ideally, the testing program must be written in the same language you testing against.

Testing is such a common activity with JavaScript, especially with the rise of JS frameworks Angular, React, Vue, Ember  ... Most of these frameworks come with the unit and e2e testing incorporated. And so rise of testing frameworks: Enzyme, Jest, Karma, Jasmine, Sinon to name but a few.

These testing frameworks and libraries greatly reduce the time it takes to write tests. Angular in particular has its default testing frameworks: Jasmine and Karma.


Jasmine

Jasmine is the most popular testing framework in the JS framework world. It supports a software development practice called Behaviour Driven Development or BDD for short. IT’s a subset of Test Driven Development (TDD). Jasmine tries to describe tests in a human-readable form, so that non-tech. people can really understand what is being tested. Jasmine has several functions which come in handy. The general idea to note is that the Jasmine function executes a given test function to check if it produces the same result as the expected result.


Karma

Karma is a task runner for tests. It is a test automation utility for managing and controlling the execution of tests. It spawns the browser and runs the tests inside of them.

Angular apps are run in a browser environment. We could use any browser Chrome, Firefox, Safari. So, here we practically load our test(s) in any browser to run and display the result on the browser. You know, it gets boring and stressful reloading/refreshing the browser all time to see your test(s) results.

In comes Karma to save the day!!! It spawns the browsers and runs the Jasmine tests inside of them and display the result in our terminal. Not only that, it runs idly looking for file change(s) and re-runs the test(s) if any change is caught.

karma


TestBed

TestBed (or Angular TestBed) is an Angular-specific testing framework that provides Angular behavior to test environment. It supports test(s) written/dependent on the Angular. Jasmine or Karma won’t provide services like Dependency Injection, Change Detection mechanism of Angular, Components or Directives or Pipes interaction with templates, Angular Compilation technique. So, TestBed actually, lets us run test(s) based-on Angular.

We are going to use Jasmine and Karma to test our module and classes. Of course!! As stated earlier they are the frameworks/libraries for testing Angular apps/modules. In addition to Jasmine and Karma, we going to employ another framework, karma-typescript.


Karma-TypeScript

Jasmine is for testing JS files, but Angular apps/modules are written in TS. To test TS files it must be first compiled to JS before Jasmine can run the tests. Karma-TypeScript compiles the TS files for us and passes the compiled files to Jasmine to run them. Like other testing frameworks/libraries, it should be installed as a development dependency.

Let’s begin by installing our testing libraries/frameworks. Let’s install Jasmine, Karma, and Karma-TypeScript first:

npm i jasmine-core karma karma-typescript -D

Let’s add some plugins we will also need:

npm i @types/jasmine @types/node karma-chrome-launcher karma-coverage-istanbul-reporter karma-jasmine karma-typescript-angular2-transform typescript -D

Next, we create and configure Jasmine, Karma and Karma-TypeScript in a configuration script file karma.conf.js:

// karma.conf.js
module.exports = function(config) {
   config.set({
       frameworks: ["jasmine", "karma-typescript"],
       files: [
           { pattern: "src/**/*.ts" },
           { pattern: "test/**/*.ts" }
       ],
       preprocessors: {
           "src/**/*.ts": ["karma-typescript", "coverage"],
           "test/**/*.ts": ["karma-typescript"]
       },
       reporters: ["progress", "coverage", "karma-typescript", "kjhtml"],
       client: {
           clearContext: false // leave Jasmine Spec Runner output visible in browser
       },
       coverageIstanbulReporter: {
           reports: ['html', 'lcovonly'],
           fixWebpackSourcePaths: true
       },
       karmaTypescriptConfig: {
           tsconfig: "./tsconfig.spec.json"
       },
       browsers: ["Chrome"],
       port: 9876,
       colors: true,
       logLevel: config.LOG_INFO,
       autoWatch: true,
       singleRun: false
   });
};


In order to serve our tests files, Karma needs to know about our project and it’s done through a configuration file, karma.conf.js. The configuration file informs Karma testing frameworks to use, file(s) to process, preprocessors to use, the type of browser it should spawn the port on which to serve the tests and so many other useful options.

Now we create 

tsconfig.json:

tsc --init

The above command will create a tsconfig.json file in our project's root folder. tsconfig.json is a TypeScript configuration file that tells TypeScript how to compile our project's TS files to JS.

Make sure your tsconfig.json looks like this:

{
   "compilerOptions": {
       "target": "es5",
       "module": "es2015",
       "lib": [
           "es2015",
           "dom"
       ],
       "declaration": true,
       "sourceMap": true,
       "outDir": "dist",
       "rootDir": ".",
       "strict": true,
       "noImplicitAny": true,
       "strictNullChecks": true,
       "moduleResolution": "node",
       "baseUrl": ".",
       "paths": {
           "@angular/core": ["node_modules/@angular/core"],
           "rxjs/*": ["node_modules/rxjs/*"]
       },
       "inlineSources": true,
       "experimentalDecorators": true,
       "skipLibCheck": true,
       "stripInternal": true
   }
}


We need to create another tsconfig.json for our tests. karma-typescript will need info on how to compile our TS unit tests files. Looking back at our karma.conf.js file, we set a property karmaTypescriptConfig.tsconfig to tsconfig.spec.json. 

This tells karma-typescript which configuration file to use when compiling our TS files for testing.

OK, let’s create tsconfig.spec.json:

touch tsconfig.spec.json

Make it look like this:

{
   "compilerOptions": {
       "target": "es5",
       "module": "commonjs",
       "lib": [
           "es2015",
           "dom"
       ],
       "declaration": true,
       "sourceMap": true,
       "outDir": "dist",
       "rootDir": ".",
       "strict": true,
       "noImplicitAny": true,
       "strictNullChecks": true,
       "moduleResolution": "node",
       "baseUrl": ".",
       "paths": {
           "@angular/core": ["node_modules/@angular/core"],
           "rxjs/*": ["node_modules/rxjs/*"]
       },
       "types": [
           "jasmine",
           "node"
       ],
       "files": [
           "test/test.ts"
       ],
       "include": [
           "**/*.spec.ts"
       ],
       "inlineSources": true,
       "experimentalDecorators": true,
       "skipLibCheck": true,
       "stripInternal": true
   }
}

Let’s add an NPM script so that we can conveniently start Karma from the terminal:

{
   "script": {
       "test": "karma start"
   }
}


So now we can run the command npm run test in our terminal, it will invoke the karma start command.


Angular module setup

Setting up an Angular module library is very easy it’s almost the same as creating a module in an Angular app.

When creating an Angular module, there are some points we should note:

  • Never import BrowserModule, it is always imported by the consuming root module (AppModule).

  • Export all public Components, Directives, Pipes in the exports array.

  • Export all public classes (Services) in the providers array.

  • Import the CommonModule when the need to use *NgFor, *NgIf arises.

  • Classes in the providers array are available everywhere in the app.

  • Components/Directives/Pipes declared only in declarations array are only accessible inside your module.

With these points in mind, let’s install dependencies we will need to build our Angular library:

npm i @angular/common @angular/compiler @angular/core @angular/cli @angular/compiler-cli rxjs
-D

These are core Angular libraries. We need for sure because we are developing a library for the Angular framework. Let’s go through what just installed:

  • @angular/common: This holds common directives that are used in Angular apps (*NgIf, *NgFor, *NgSwitch, *NgClass, *NgStyle).

  • @angular/compiler: This is a great piece of engineering. It compiles our components/directives etc into JS factories.

  • @angular/core: This is the citadel of Angular. It is responsible for bootstrapping our app and loading our templates/components/directives/factories(Component, Directive and NgModule etc) into the browser DOM.

  • @angular/cli: This library exposes the ng command to users. It provides several command-line utilities:

  • build: This compiles and builds Angular apps for deployment.

  • serve: This builds and serves Angular apps over localhost:4200.

  • test: This sub-command runs units and end-to-end testing in Angular apps.

  • @angular/compiler-cli: This compiles our files in an Angular specific way. It is invoked using the ngc command.

  • rxjs: Angular makes heavy use of RxJS. Most of its parts are powered by RxJS. RxJS is a JS library that brings Reactive programming to JS.

We are done installing our core libraries. Now, we begin implementing our Http module. We will use TDD approach here. We first write a failing test then, augment the production code to make the test pass.

Our Http module will implement the same concepts of the Angular Http module.

We will use the notion of backends to build our module. In a browser environment, XMLHttpRequest is used to query resources over a network. Different environments have different methods/implementation of querying resources. In a Node.js environment XMLHttpRequest cannot be used and it does not exist there. Also, during tests querying resources should be done using mocks, in this case, maybe a backend should be mock-ed.

So different environments use different backend implementation:

  • Browser uses BrowserBackend

  • Node.js uses NodeBackend

  • Mock uses MockBackend

We are just trying to build our Http library in such a way users can plugin their own backend implementation if needed.

import {Http, MyNodeBackend, HTTP_PROVIDERS, BaseRequestOptions} from '@angular/http';
@Component({
   viewProviders: [
       HTTP_PROVIDERS,
       {provide: Http, useFactory: (backend, options) => {
       return new Http(backend, options);
       }, deps: [MyNodeBackend, BaseRequestOptions]}]
})
class MyComponent {
   constructor(http: Http) {
       http.request('people.json').subscribe(res => this.people = res.json());
   }
}

We will build the default browser backend because it will be used in an Angular app which will, of course, run in a browser environment.

We will first implement our XMLHttpRequest backend. Let's create a folder backend/ inside our src/ folder. Next, create backend/xhr_backend.ts.

We are going to start our karma daemon, as we are using a TDD methodology. We also need to put a base test that initializes our Angular TestBed environment.

Create test/base.spec.ts and add the following contents inside:

// test/base.spec.ts
import "core-js"
import "zone.js/dist/zone";
import "zone.js/dist/long-stack-trace-zone";
import "zone.js/dist/proxy";
import "zone.js/dist/sync-test";
import "zone.js/dist/jasmine-patch";
import "zone.js/dist/async-test";
import "zone.js/dist/fake-async-test";
import { TestBed,getTestBed } from "@angular/core/testing";
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from "@angular/platform-browser-dynamic/testing";
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());


We imported core-js, zone.js, then also imported TestBed, BrowserDynamicTestingModule, platformBrowserDynamicTesting from Angular. These make it possible to test Angular-dependent code.

In your terminal, run the test command:

npm run test

This command will go through a series of steps invoking its dependencies, launching the Chrome browser, compiling all tests file in test/ folder and finally, displaying the tests results on Chrome and on the terminal. Also, it(karma) will sit idly watching for file changes and will re-run the tests whenever a file change occurs.


Implementing Browser Backend

As we stated earlier, our module will use the notion of the backend to know how to access a resource depending on the environment it’s running. Browser-based HTTP calls cannot be used in a Node.js environment. Also, during tests HTTP calls are mocked, so a backend should be developed for tests only. This means, you plugin the backend for the environment.

We will be developing the backend for a browser in this article. You can practicalize what you have learned here by adding different backends for different environments.

In the implementation of our backend, we will be creating three classes: XHRBackend, XHRConnection, and XHRBrowser.

XHRBackend will be responsible for creating and returning a connection. XHRConnection completes the request on instantiation. XHRBrowser will create and return the XMLHttpRequest object.

XHRBrowser

XHRBrowser class will contain a method build that will return an instance of XMLHttpRequest. Let's make our first test case to implement this behavior. create test/xhrbrowser.spec.ts file and add this code:

import {TestBed, inject} from '@angular/core/testing';
import { XHRBrowser } from '../src/backend/xhr_backend';
describe('XHRBrowser', () => {
 beforeEach(() => {
   TestBed.configureTestingModule({
     providers: [
       XHRBrowser,
     ]
   });
 });
 it('`xhr.build()` should return an instance of XMLHttpRequest', inject([XHRBrowser], (xhr: XHRBrowser) => {
   expect(xhr.build() instanceof XMLHttpRequest).toBe(true);
 }));
});


Here, we imported TestBed, inject and non-existentent XHRBrowser. Next, we defined our first test suite using the describe function. In the beforeEach function, we configured a testing module using the TestBed class. This creates a testing Angular module that can be used to provide Directives, Components, Providers and so on.

We provided our XHR Browser class in the providers array. With this we can get the instance of XHR Browser using Injector or inject. We defined a test spec using the it function, see how inject was used to get the instance of XHR Browser and pass it to the function argument.

At last, we defined our test case, calling the build() method of the XHRBrowser instance, xhr. We expect it to return the instance of XMLHttpRequest.

NB: This test will fail, this is exactly what we want because an important step in test-driven development is seeing the test fail first.

To make this test pass, we are going to define XHRBrowser class in src/backend/xhrbackend.ts:

// src/backend/xhrbackend.ts
import {Injectable} from '@angular/core'
@Injectable()
export class XHRBrowser {
}

Let’s create a method build to return an instance of XMLHttpRequest:

// src/backend/xhrbackend.ts
import {Injectable} from '@angular/core'
@Injectable()
export class XHRBrowser {
   build() {
       return new XMLHttpRequest()
   }
}


That’s it, we created an instance of XMLHttpRequest using the new keyword and returned it using the return keyword. Now, our test passes.

XMLHttpRequest is a built-in class in browser environment used to access resources over a network. In other hands, it used to connect your app to the internet.

XHRConnection

This creates connections using XMLHttpRequest. When given a request and an XHR Browser instance, it initializes an Observable with an observer that creates XMLHttpRequest object, assign events to the object and sends the request. The Observable instance is assigned to property response with type Observable. This response property is what is returned when a connection is created. This is the reason, users have to use subscribe function to get their data:

this.http.get('books.json').subscribe(res => this.books = res.json())

The data is next-ed into the data stream by XHRConnection and returning the data stream, response of type Observable.

Let’s begin the implementation, first create test/xhrconnection.spec.ts. Before we add any test case, we are going to create a mock of XHR Browser. Why? XHRConnection uses the build method XHRBrowser provides to create XMLHttpRequest object, and we wouldn't want to access a real network during tests and maybe the servers may be down. What we need to do is to go without real HTTP communication and fake the servers. What we are going to do is to mock XMLHttpRequest, yes, I said XHRBrowser before. We will extend XHRBrowser functionality to override the build method so that we can return our own mock XMLHttpRequest.

Now, we define two classes MockXMLhttpRequest and MockXHRBrowser:

// test/xhrconnection.spec.ts
let abortSpy: any;
let sendSpy: any;
let openSpy: any;
class MockXMLHttpRequest  {
   response: any
   status: number
 constructor() {
   abortSpy = spyOn(this,'abort');
   sendSpy = spyOn(this,'send');
   openSpy = spyOn(this,'open');
 }
 abort() {}
 send () {}
 open() {}
 setResponse(response: any) { this.response = response}
 setStatus(status:number) { this.status = status}
 dispatchEvent(type: string) {
   (this as any)[type]()
 }
}
class MockXHRBrowser extends XHRBrowser {
   mockXMLHttpRequest:MockXMLHttpRequest
   build() {
       this.mockXMLHttpRequest = new MockXMLHttpRequest()
       return this.mockXMLHttpRequest
   }
}


In MockXMLHttpRequest class, you see we defined the core methods found in XMLHttpRequest open, send and abort. We used spyOn to mock the functions so that we can know whether the function is called, the return value and the arguments. Also, there several setters that we can use to manipulate different properties that XMLHttpRequest provides. Last we have a dispatchEvent method, this will be used to mimic different XMLHttpRequest events like onload, onerror, ontimeout. So you see with this we can simulate different scenarios and test against them.

Looking at the second class we defined, MockXHRBrowser. We overrode the build method so that we can return our MockXMLHttpRequest instance.

OK, now we are done with creating mock classes. Let’s begin implementing XHRConnection class.

As we said before, XHRConnection creates an Observable instance and assign it to a response property.

Good, let’s add a test case that asserts an XHRConnection instance was created. Remember, XHRConnection should take in a request object and XHRBrower instance in its constructor.

// test/xhrconnection.spec.ts
import { XHRConnection, XHRBrowser } from '../src/backend/xhr_backend';
...
describe('XHRConnection', () => {
 it('should be created', () => {
   expect(new XHRConnection({}, new MockXHRBrowser())).toBeTruthy();
 });
})


To make this pass we define XHRConnection in src/backend/xhr_backend.ts:

// src/backend/xhr_backend.ts
...
export class XHRConnection {
   response: Observable<any>
   constructor(requestOptions: any, browserXHR: XHRBrowser) { }
}


Here, we created XHRConnection class with a constructor that takes in a request object and a XHRBrowser instance. Also, we defined a response property.

We need to instantiate an Observable and assign it to response. Let’s add a test case that checks for this:

// test/xhrconnection.spec.ts
...
import { Observable } from 'rxjs';
...
describe('XHRConnection', () => {
...
 it('`response` should be instance of Observable', () => {
     const conn = new XHRConnection({}, new MockXHRBrowser())
   expect(conn.response instanceof Observable).toBeTruthy();
 });
})


We assigned XHRConnection instance to conn, and used instanceof keyword to test conn.response is an instance of Observable.

Now, in XHRConnection class, we initialize an Observable instance and assign it to the response property:

// src/backend/xhr_backend.ts
...
export class XHRConnection {
   response: Observable<any>
   constructor(requestOptions: any, browserXHR: XHRBrowser) {
       this.response = new Observable(function (observer: any) { })
   }
}


The function arg will be called with an observer function whenever the response is subscribed to. The observer is where we will yield the response of the Ajax call. With these our test case passes.

Now, to make Ajax calls with an Observable, we will wrap XMLHttpRequest inside the function argument. First, we create the XMLHttpRequest instance and call the open() method with our request method and the URL as arguments. Event listeners are registered to capture events XMLHttpRequestemits, then the request is sent using the send() method.


Example

let xHR = new XMLHttpRequest()
xHR.open('POST', 'https://google.com?search=rxjs')
xHR.onload = function() { this.rxjs = xHR.response }
xHR.send()


Above is a typical example of how to make a simple Ajax request. We constructed an XMLHttpRequest object, then, opened a connection, telling it the type of request (GET, POST, DELETE, PUT or OPTIONS) we want to make and the resource URL. An onload event was registered, it assigns the response of the call to rxjs variable. The onload event is emitted when the Ajax call is successful. Next, we initiate the call using the send method.

As we have seen, the onload event is where we get our response. So, in our own case, we will yield the response to the observer function and complete the sequence.

Let’s instantiate XMLHttpRequest object:

// src/backend/xhr_backend.ts
...
export class XHRConnection {
   response: Observable<any>
   constructor(requestOptions: any, browserXHR: XHRBrowser) {
       this.response = new Observable(function (observer: any) {
           let httpRequest = browserXHR.build();
       })
   }
}


We assigned the XMLHttpRequest object to httpRequest. Next, we open a connection using the open() method. Let's add a test case that checks the open method is called when we subscribe to the Observable.

// test/xhrconnection.ts
...
describe('XHRConnection', () => {
...
 it('`open` should be called', () => {
   const conn = new XHRConnection({}, new MockXHRBrowser())
   conn.response.subscribe()
   expect(openSpy).toHaveBeenCalled()
 });
})


Note, the function arg in the Observable is only called when at laest on Observer subcribes to it. So here, instantiated XHRconnection and subcribed to the response Observable. We expect the spy function openSpy to be called.

To make this pass, we simply call the open function in our XHRConnection class:

// src/backend/xhr_backend.ts
...
export class XHRConnection {
   response: Observable<any>
   constructor(requestOptions: any, browserXHR: XHRBrowser) {
       this.response = new Observable(function (observer: any) {
           let httpRequest = browserXHR.build();
          httpRequest.open(requestOptions.method, requestOptions.url);
       })
   }
}

We register our listeners, we are going to register the onload listener. The onload event is called whenever there is a successful Ajax call. So we hook into it to get the response.

Let’s add the onload listener, first we add a case that asserts that onload function exists when the subscribe function is called:

// test/xhrconnection.spec.ts
...
describe('XHRConnection', () => {
...
 it('onload listener should exist on subscribe call', () => {
     const mockXHRBrowser = new MockXHRBrowser()
     const conn = new XHRConnection({}, mockXHRBrowser)
     conn.response.subscribe()
    expect((mockXHRBrowser.mockXMLHttpRequest as any)['onload']).toBeTruthy()
 });
});


Making it pass we do this:

// src/backend/xhr_backend.ts
...
export class XHRConnection {
   response: Observable<any>
   constructor(requestOptions: any, browserXHR: XHRBrowser) {
       this.response = new Observable(function (observer: any) {
           let httpRequest = browserXHR.build();
          httpRequest.open(requestOptions.method, requestOptions.url);
          httpRequest.onload = function() {
          }
       })
   }
}


OK, we registered for the onload event. Now, we going to flesh out the function. Here, we receive the result of the Ajax/HTTP call in a property response and yield it to the observer using next method. In reactive programming, next is used to put a value to a data stream so that its observers can get the data in its next function.

// src/backend/xhr_backend.ts
...
export class XHRConnection {
   response: Observable<any>
   constructor(requestOptions: any, browserXHR: XHRBrowser) {
       this.response = new Observable(function (observer: any) {
           let httpRequest = browserXHR.build();
          httpRequest.open(requestOptions.method, requestOptions.url);
          httpRequest.onload = function() {
               observer.next(httpRequest.response)
               observer.complete()
          }
       })
   }
}


We supply the response to the observer via its next function and we tell the observer that’s the end of the data stream by calling its complete function.

observers are just objects


We are done with our onload event function. Like we said earlier, for XMLHttpRequest to send the request over a network, we need to call the sendmethod.

Let’s add a test that asserts that the send method is called when the response Observable is subscribed to:

// test/xhrconnection.spec.ts
...
describe('XHRConnection', () => {
...
 it('`send` should be called', () => {
   const conn = new XHRConnection({}, new MockXHRBrowser())
   conn.response.subscribe()
   expect(sendSpy).toHaveBeenCalled()
 });
})

Simple, we call the send method:

// src/backend/xhr_backend.ts
...
export class XHRConnection {
   response: Observable<any>
   constructor(requestOptions: any, browserXHR: XHRBrowser) {
       this.response = new Observable(function (observer: any) {
           let httpRequest = browserXHR.build();
          httpRequest.open(requestOptions.method, requestOptions.url);
          httpRequest.onload = function() {
               observer.next(httpRequest.response)
               observer.complete()
          }
          httpRequest.send()
       })
   }
}


We are done with our XHRConnection. To recap on what we did here. We created a constructor that takes in a request object and XHRBrowser instance, then, we instantiated an Observable with a function arg that creates and send an HTTP call using XMLHttpRequest, the result is fed to an observer. The instance of the Observable is assigned to a property response. This response is what will be returned to the users so that they can subscribe to it and get their data.

XHRBackend

XHRBackend returns an instance of XHRConnection. It typically creates the connection to the backend. It doesn’t have to know the type of connection, it’s left for the XHRConnection class to provide the kinda connection that is ideal for the current environment.

To begin, let’s create an XHRBackend class in src/backend/xhr_backend.ts:

// src/backend/xhr_backend.ts
...
@Injectable()
export class XHRBackend {
}

Here, we just defined our XHRBackend class, we also annotated it with the Injectable decorator, this tells Angular to mark this class for Dependency Injection.

XHRBackend will take the instance of XHRBrowser in its constructor, so we can feed it to XHRConnection when we are creating a connection. Passing an instance of a class is one of the best practices in programming so that our code would be easy to test and maintain.

As always we write a failing test first before that create this test/xhrbackend.spec.ts file. Then, add this test case:

// test/xhrbackend.spec.ts
import {TestBed, inject} from '@angular/core/testing';
import { XHRBackend, XHRConnection, XHRBrowser } from '../src/backend/xhr_backend';
import {MockXHRBrowser} from './xhrconnection.spec'
describe('XHRBackend', () => {
beforeEach(() => {
   TestBed.configureTestingModule({
     providers: [
       { provide: XHRBrowser, useClass: MockXHRBrowser },
       {
           provide:XHRBackend,
           useFactory: (xHRBRowser:XHRBrowser) => {
               return new XHRBackend(xHRBRowser)
           },
           deps:[XHRBrowser]
       }
     ]
   });
});
it('should be created', inject([XHRBackend], (xhr: XHRBackend) => {
   expect(xhr).toBeTruthy();
 }));
})


We imported classes and functions we will be using. Then, we configured our Testing module to provide our classes. See, we used the useClass property to tell Angular to provide our MockXHRBrowser as XHRBrowser. Next, we declared a test spec that checks XHRBackend instance is created.

Let’s configure our XHRBackend constructor to accept an XHRBrowser instance:

// src/backend/xhr_backend.ts
...
@Injectable()
export class XHRBackend {
   constructor(private browserXHR: XHRBrowser) {  }
}


Next, we will create a method createConnection this method will take an object as arg and create an instance of XHRConnection.

Let’s add a test case that checks createConnection returns an instance of XHRConnection:

// test/xhrbackend.spec.ts
...
describe('XHRBackend', () => {
...
it('should return XHRConnection instance', inject([XHRBackend], (xhr: XHRBackend) => {
   expect(()=> xhr.createConnection({})).not.toThrow();
   expect(xhr.createConnection({}) instanceof XHRConnection).toBeTruthy();
 }));
})


You see here, we made sure the createConnection method exists and doesn’t throw. Also, we checked the return value of createConnection is an instance of XHRConnection.

To make this test pass, we simply define the createConnection method:

// src/backend/xhr_backend.ts
...
@Injectable()
export class XHRBackend {
   constructor(private browserXHR: XHRBrowser) {  }
   createConnection(requestOptions: any) {
   }
}


Next, we make it return an instance of XHRConnection:

// src/backend/xhr_backend.ts
...
@Injectable()
export class XHRBackend {
   constructor(private browserXHR: XHRBrowser) {  }
   createConnection(requestOptions: any) {
       return new XHRConnection(requestOptions,this.browserXHR)
   }
}


Wow!!!, we are doing great. Now, our XHRBackend implementation is complete. To recap what we did here, we created XHRBackend class, with a method createConnection that returns an XHRconnection instance. This class would not be used by users but it could be overriden to provide an implementation for a different environment.


Implementing Http class

Finally, we are to implement the class that will be used by users. This class will be used by end-users to make different types of HTTP calls like GET, POST, DELETE or PUT.

Each HTTP method will be exposed to them as methods in the Http class.

To begin implementing our Http class, we first create src/Http.ts file and define the Http class in it:

// src/Http.ts
export class Http {
}

Http has to be initialized with an instance of XHRBackend. Let’s create test/http.spec.ts file and add the following test case in it:

// test/http.spec.ts
import {TestBed,  inject} from '@angular/core/testing';
import { Http } from './../src';
import { XHRBackend, XHRBrowser } from '../src/backend/xhr_backend';
import {MockXHRBrowser} from './xhrconnection.spec'
describe('Http', () => {
 beforeEach(() => {
   TestBed.configureTestingModule({
     providers: [
       {
         provide: Http,
         useFactory: (xhr: XHRBackend) => {
           return new Http(xhr)
         },
         deps:[XHRBackend]
       },
       {provide: XHRBrowser, useClass: MockXHRBrowser},
       {provide:XHRBackend, useFactory: (x:XHRBrowser)=>{
         return new XHRBackend(x)
       }, deps:[XHRBrowser]}
     ]
   });
 });
 it('should be created', inject([Http], (http: Http) => {
   expect(http).toBeTruthy();
 }));
});


As before we imported classes and functions that will be needed, then, configured a Testing module. We add a spec that asserts an Http instance was created.

To make this pass, we add a constructor to Http that takes in an XHRBackend instance.

// src/Http.ts
import { XHRBackend } from './backend/xhr_backend';
export class Http {
   constructor(private backend: XHRBackend) {}
}

Now, we said earlier that we define methods in the Http class with the name as the HTTP Verb they request.

For example, get method in our Http class will request a GET method.

this.http.get('fishes.json')

post method will deliver an Ajax call with POST method.

this.http.post('localhost:5000/books', { books: ['art of war','inferno'] })

Also, we need to define a method request that delivers any type of HTTP call. Typically, get, post etc methods will call request method with its own specific HTTP method. get will call request with HTTP method set to GET, post with POST and so on. request method will create an XHRConnection with request URL and METHOD, then return the response Observable.

Enough of talk, let’s make it happen. Let’s add test case that checks request method returns an Observable:

// test/http.spec.ts
describe('Http', ()=> {
...
 it('request should return an Observable', inject([Http], (http: Http) => {
   http.request({}).subscribe()
   expect(http.request({}) instanceof Observable).toBeTruthy();
 })
})


Making it pass, we define request method in our Http class and make it return the response property from an XHRConnection instance:

// src/Http.ts
...
import { Observable } from 'rxjs';
...
export class Http {
   constructor(private backend: XHRBackend) {}
   request(requestOptions: any): Observable<any> {
       return this.backend.createConnection(requestOptions).response
   }
}


We used the backend object to create a connection using createConnection method we defined earlier in XHRBackend. The method returns an instance of XHRConnection, so we access the response property and return it.

Now, we define our HTTP METHOD specific methods, get, post etc.

We are only going to implement get method. post, delete etc methods should be implemented by the reader. You know, practicalizing what you have learned is a sure way of thoroughly understanding a concept.

OK, let’s write a test case that asserts that get method exist and is a function: \

// test/http.spec.ts
describe('Http', ()=> {
...
  it('checks get method exist on Http instance', inject([Http], (http: Http) => {
    expect(typeof http.get).toBe('function');
  }));
});

To make this pass we define get method on Http:

// src/Http.ts
...
export class Http {
    constructor(private backend: XHRBackend) {}
    request(requestOptions: any): Observable<any> {
        return this.backend.createConnection(requestOptions).response
    }
    get (url: string, options?: any) {  }
}

To make sure it returns an Observable, let’s add this test:

// test/http.spec.ts
describe('Http', ()=> {
...
  it('get() call should return an Observable', inject([Http, XHRBrowser], (http: Http,xhrB:XHRBrowser) => {
    expect(http.get('people.json') instanceof Observable).toBeTruthy();
  }));
});


To make it pass, we call request method in get method body, passing in objects with properties set to GET and URL:

// src/Http.ts
...
export class Http {
    constructor(private backend: XHRBackend) {}
    request(requestOptions: any): Observable<any> {
        return this.backend.createConnection(requestOptions).response
    }
    get (url: string, options?: any) {
        return this.request({ url, method: 'GET' })
    }
}


OK, our Http class implementation is now complete. Users can now use our Http class to query resources over network using the request and get methods.


Implementing HttpModule

In order to use our classes in an Angular environment, we will create a NgModule and provide all our classes in the providers array. This is done so that all our classes would be available app-wide i.e. Any of our classes could be imported and used anywhere on the app.

To begin, let’s create src/HttpModule.ts file and add the following:

// src/HttpModule.ts
import { NgModule } from '@angular/core'
import { Observable } from 'rxjs';
import { Http } from './Http'
import { XHRBackend, XHRBrowser } from './backend/xhr_backend';
function httpFactory(Xhrbackend: XHRBackend) {
    return new Http(Xhrbackend)
}
@NgModule({
    imports: [],
    declarations: [],
    exports: [],
    providers: [
        {
            provide:Http,
            useFactory: httpFactory,
            deps: [XHRBackend]
        },
        XHRBrowser,
        XHRBackend
    ]
})
export class HttpModule {}


We define the HttpModule class and annotated it with a NgModule decorator. Most of the properties in the NgModule decorator function are empty expect the providers key, that’s because our module consisted only of plain classes. Passing the classes in the providers array tells Angular to make them available throughout the app.

Exporting the public API

We will create src/index.ts file to export all of our public API. It will contain our NgModule and all classes we want to be used by our users.

// src/index.ts
export { Http } from './Http';
export { HttpModule } from './HttpModule';

Publishing our library

Before we publish our library we have to bundle it. To bundle our library, we need to install some tools:

  • rollup: For bundling all our files into a single file.
  • uglify-js: For minifying our code.
npm i rollup uglify-js -D


Create src/rollup.config.js file. This tells Rollup how to bundle our code files. Add the following code to it:

// src/rollup.config.js
export default {
    input: 'dist/src/index.js',
    output: {
        sourceMap: false,
        dir: 'dist/bundles/',
        file: 'dist/bundles/ng.http.umd.js',
        format: 'umd',
        name: 'ng.http',
        globals: {
            '@angular/core': 'ng.core',
            'rxjs/Observable': 'Rx',
            'rxjs/ReplaySubject': 'Rx',
            'rxjs/add/operator/map': 'Rx.Observable.prototype',
            'rxjs/add/operator/mergeMap': 'Rx.Observable.prototype',
            'rxjs/add/observable/fromEvent': 'Rx.Observable',
            'rxjs/add/observable/of': 'Rx.Observable'
        }
    }
}


  • input: The entry point of the bundling process.
  • output: Info on how to spit out the bundled files.
  • output.sourceMap: declares whether to generate sourcemap files for debugging purposes.
  • output.dir: Folder to store the bundled file
  • output.file: Name of the bundled file.
  • output.format: Angular uses the UMD format to deliver its modules, so ours should be umd.
  • output.globals: This tells Rollup which dependencies not to include in the bundling process and set it as a global. This means that the user app of this library will already have the dependencies. There will be some complexities and errors, if the library installs the already-present libraries.

We will add some npm scripts to automate the bundling process of our files.

We have to transpile our src files in an Angular way using ngc command, then, we bundle the transpiled files and lastly, minify the bundled file.

Add these to our package.json:

// src/package.json
...
    "main": "dist/bundles/ng.http.umd.min.js",
    "module": "dist/index.js",
    "typings": "dist/index.d.ts",
    "scripts": {
        "test": "karma start",
        "transpile": "ngc",
        "package": "rollup -c",
        "minify": "uglifyjs dist/bundles/ng.http.umd.js --screw-ie8 --compress --mangle --comments --output dist/bundles/ng.http.umd.min.js",
        "build": "npm run transpile && npm run package && npm run minify"
    },
...


We have transpile script for running the Angular compiler against our files, package script for bundling the files using Rollup, minify script for minifing our bundled file in dist/bundles/ng.http.umd.js using uglify-js. The build script runs each of them synchronously to achieve a perfect build, dist/bundles/ng.http.umd.min.js.

The main property points to dist/bundles/ng.http.umd.min.js, the final output because this is the file our users will be referring to when consuming our module.

To see all of this workout, run the command: 

npm run build

Voila!! Our final bundle should reside in dist/bundles/ng.http.umd.min.js now.

We need to remove some redundant files that shouldn’t be published alongside our module folder, dist. To do this we add an .npmignore file and put down the name of files and folder we want NPM to ignore:

// src/.npmignore
.*.swp
._*
.git
.hg
.DS_Store
.npmrc
.svn
.lock-wscript
.wafpickle-*
config.gypi
CVS
npm-debug.log
src/

Run this command to publish our module:

npm publish

NB: Make sure you give your project a name via the name key in package.json. So that users can install your library via its name from the npmjs registry. Mine is ng.http.lib, so it can be installed like this npm i ng.http.lib -S.

Running an Example app

Now we have published our module, let’s see how it works in a small Angular app. We scaffold a barebones Angular and pull in our module. We will use our module instead of Angular’s built-in Http module.

ng new test-app --minimal

We scaffold a new Angular project. The minimal flag tells ng to create for us an Angular project without any test files and all CSS, HTML should be inline.

Next, we pull in our module:

npm i ng.http.lib<THE_NAME_OF_YOUR_MODULE> -S

We import HttpModule from our library and provide it in the imports array of AppModule:

// test-app/src/app/app.module.ts
...
import { HttpModule } from 'ng.http.lib';
@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    HttpModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Now, we can use the Http class in any Component/Class. We would pass our Http class into AppComponent constructor, then implement the OnInit interface. We will define ngOnInit method so that it will be called after our component instantiation.

Inside the ngOnInit method, we will call the get method to make an HTTP call with our module. It will fetch a message that we will assign to the title property.

First, let’s create a server.js file where our get method would query for data.

// test-app/server.js
const http = require('http')
http.createServer(function(req, res) {
        res.setHeader('Access-Control-Allow-Origin', '*')
        res.setHeader('Access-Control-Allow-Methods', ['GET', 'POST'])
        res.end("Message from Server")
    })
    .listen(3000, () => {
        console.log(`Server listening on port: 3000`)
    })

We required the http module, then created a server using createServer() function and made it listen on port 3000. On access it returns the message Message from Server.

We are done with the server. Let’s implement ngOnInit method:

// test-app/src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { Http } from 'ng.http.lib';
@Component({
  selector: 'app-root',
  template: `
    <div style="text-align:center">
      <h1>
        Welcome to {{title}}!
      </h1>
    </div>    
  `,
  styles: []
})
export class AppComponent implements OnInit {
  title = 'app';
  constructor(private http: Http){}
  ngOnInit() {
    this.http.get('http://localhost:3000').subscribe((res:any)=>{
      this.title = res
    })
  }
}

We used the get method to query a network, then subscribed to the stream to receive data. Then, we update the property title with the data received. To see it in action. First, run the server.js file:

node server.js

Then, on another terminal, in the root directory of the project, start the ng server:

ng serve

Open your browser and navigate to localhost:4200. You will see "Welcome to Message from Server" displayed on your browser.

With our own custom HTTP module, we successfully accessed and fetched data over network !!! We can now substitute our module for Angular’s built-in HTTP module.

Conclusion

We learned a lot of things during the course of this article:

  • Reactive programming.
  • Observables and Observers.
  • Angular modules and how to set up a new Angular module.
  • Testing frameworks: Jasmine, Sinon, Karma.
  • How to setup up tests for Angular projects.
  • How to build apps using TDD approach.
  • How to test Angular modules/project using TestBed.

I know we violated a lot of best practices and some things that should have been done in a more clever way, the most important thing is that we learned how to build a reactive http library for Angular and also, some concepts about modern web app development.

Like I said earlier you can expand the application to add more functionality, I’ll be glad to hear your stories and feedback. Thanks !!!

Chidume

Chidume Nnamdi

Author

JavaScript Ninja | Angular Archangel | Guest Writer @auth0 | Machine Learning freak | AI enthusiast | Technical Writer

Leave a Reply

Your email address will not be published. Required fields are marked *

Top comments

sabella Isla

27 November 2018 at 1:30pm
very nice blog !! Guy .. Excellent .. Amazing .. I will bookmark your web site. . . .

SUBSCRIBE OUR BLOG

Follow Us On

Share on

other Blogs

20% Discount