by Horatiu Dan
Nowadays, most products have their front-ends and back-ends fully decoupled and sometimes developed by different teams. Once the communication interfaces have been agreed upon, the ‘parts’ may be developed either individually, following distinct paths, or together. Irrespective of this aspect, when developing the front-end it is very convenient to be able to focus on its main functionalities and the interaction with the back-end and treat the latter as a black-box.
This post presents a few ways of mocking the data consumed in an Angular application, so that the evolution of the product is smooth and not completely dependent on the back-end services, especially during its development.
Set-up
The front-end application displays a page with Ninjago Series characters, together with a few attributes – name, type, season they first appear in, whether they are favorite or not.
The real data is stored in a database and provided via REST by a back-end service. The assumption is the back-end is not available at this moment, yet the front-end needs to be developed.
The Project
A character is modeled as below (character.ts
)
export interface Character { id: number; type: string; name: string; season: number; favorite: boolean; }
and rendered in the UI in as a simple component
(character.component
)
character.component.html
<strong>{{ character.name }}</strong> <div>Type: {{ character.type }}</div> <div>First seen in season: {{ character.season }}</div> <div>Favorite: {{ character.favorite }}</div> <br>
character.component.ts
import {Component, Input, OnInit} from '@angular/core'; import {Character} from './character'; @Component({ selector: 'app-character', templateUrl: './character.component.html', styleUrls: ['./character.component.css'] }) export class CharacterComponent implements OnInit { @Input() character: Character; constructor() { } ngOnInit(): void { } }
All available characters are displayed in a separate list
component (character-list.component
).
character-list.component.html
<app-character *ngFor="let character of characters" [character]="character"></app-character>
character-list.component.ts
import { Component, OnInit } from '@angular/core'; import {Character} from '../character/character'; import {CharacterService} from '../services/character.service'; @Component({ selector: 'app-character-list', templateUrl: './character-list.component.html', styleUrls: ['./character-list.component.css'] }) export class CharacterListComponent implements OnInit { characters: Character[]; constructor(private characterService: CharacterService) { } ngOnInit(): void { this.initCharacters(); } private initCharacters(): void { this.characterService.getCharacters() .subscribe(characters => this.characters = characters); } }
As shown above, the data is retrieved via the
CharacterService
that is injected in the component and
stored in the characters array.
In the following sections, 3 ways to simulate the real data flow are presented – the first one is trivial, while the next ones a little bit more interesting and useful.
Mock Data Directly
If we look at the method that populates the characters array in
the CharacterListComponent
–
initCharacters()
– we see a call to the
getCharacters()
service method which is triggered once
a subscription is fulfilled, thus the signature of the method is as
below.
getCharacters(): Observable<Character[]>
The most straight forward mock implementation of the service is
to return the stored array using the rxjs
of()
function.
import { Injectable } from '@angular/core'; import {Observable, of} from 'rxjs'; import {Character} from '../character/character'; @Injectable({ providedIn: 'root' }) export class CharacterService { private characters: Character[] = [ { id: 1, name: 'Jay', type: 'Good', season: 1, favorite: true }, { id: 2, name: 'Vanghelis', type: 'Evil', season: 13, favorite: false }, { id: 3, name: 'Overlord', type: 'Evil', season: 2, favorite: false }, { id: 4, name: 'Aspheera', type: 'Evil', season: 11, favorite: false }, { id: 5, name: 'Kay', type: 'Good', season: 1, favorite: true } ]; constructor() { } getCharacters(): Observable<Character[]> { return of(this.characters); } }
If the application is run, the data is serviced and rendered in the UI.
No styling is applied, thus the simplistic appearance.
This is the most straight-forward approach to mock this service,
it may be used in a very incipient phase of development, but it is
completely unreliable in my opinion. CharacterService
is in a temporary state and it will be surely changed when the
actual integration with the back-end is done.
The source code for this stage is here – https://github.com/horatiucd/ninjago-front-end/tree/v2.0.0.
Mock using an HttpInterceptor
Angular has a module called HttpClientModule
destined for working with HTTP calls from the client. This is an
optional module that is included if needed.
As the data between the back-end and the front-end is exchanged
via REST, CharacterService
uses
HttpClient
to trigger requests. To make it possible,
app.module.ts
imports HttpClientModule
and declares it as part of the @NgModule
imports
array.
import {HttpClientModule} from '@angular/common/http'; @NgModule({ declarations: [ AppComponent, CharacterListComponent, CharacterComponent ], imports: [ BrowserModule, AppRoutingModule, HttpClientModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
CharacterService
is modified to its final
version:
import {Injectable} from '@angular/core'; import {Observable} from 'rxjs'; import {Character} from '../character/character'; import {HttpClient} from '@angular/common/http'; import {map} from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class CharacterService { constructor(private http: HttpClient) { } getCharacters(): Observable<Character[]> { return this.http.get<Character[]>('/api/v1/characters') .pipe(map(data => { if (data === null) { return []; } return data.map((character: Character) => character); })); } }
getCharacters()
method is now in its final form –
it performs a HTTP GET to /api/v1/characters
endpoint
and if available, the data is received. As mentioned before,
currently the back-end is not available. Thus, in order for the
project to evolve, it needs to be self-contained. Instead of
interrogating the real remote server, an HTTP interceptor is used
instead, which provides the mocked data.
First the array of characters is moved to
/mocks/characters.ts
file so that is easily referred
to from now on.
export const CHARACTERS: Character[] = [ { id: 1, name: 'Jay', type: 'Good', season: 1, favorite: true }, { id: 2, name: 'Vanghelis', type: 'Evil', season: 13, favorite: false }, { id: 3, name: 'Overlord', type: 'Evil', season: 2, favorite: false }, { id: 4, name: 'Aspheera', type: 'Evil', season: 11, favorite: false }, { id: 5, name: 'Kay', type: 'Good', season: 1, favorite: true } ];
Secondly, as part of the same /mocks
directory, an
HttpInterceptor
injectable implementation is
created.
import {Injectable} from '@angular/core'; import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http'; import {Observable} from 'rxjs'; import {Character} from '../character/character'; import {CHARACTERS} from './characters'; @Injectable({ providedIn: 'root' }) export class CharacterServiceInterceptor implements HttpInterceptor { private readonly GET_CHARACTERS_URL = '/api/v1/characters'; intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { if (req.url === this.GET_CHARACTERS_URL && req.method === 'GET') { return this.getCharacters(); } return next.handle(req); } private getCharacters(): Observable<HttpResponse<Character[]>> { return new Observable<HttpResponse<Character[]>>(observer => { observer.next(new HttpResponse<Character[]>({ status: 200, body: CHARACTERS })); observer.complete(); }); } }
In general, an HttpInterceptor
allows examining and
modifying the HTTP request before passing it back to Angular’s HTTP
service. As a common use case, they might be used to add
authentication headers to every request, or to alias shorter URL to
much longer ones. As already mentioned, in case of this project it
is used to mock the HTTP requests towards the back-end service.
Interceptors have only one method
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
which returns an Observable
and takes two
arguments. The former is the HttpRequest
itself, while
the latter is the HttpHandler
used to dispatch back
the next request in a stream of requests.
The implementation is straight-forward – in case of GET requests
towards /api/v1/characters
(the same one used in the
CharacterService
), an observable containing a HTTP 200
status and the CHARACTERS
array as body is returned,
otherwise it is returned with no changes.
In order to put the interceptor in force, it needs to be wired
inside the app.module.ts
, in the providers array.
import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http'; import {CharacterServiceInterceptor} from './mocks/character-service-interceptor'; @NgModule({ declarations: [ AppComponent, CharacterListComponent, CharacterComponent ], imports: [ BrowserModule, AppRoutingModule, HttpClientModule ], providers: [ { provide: HTTP_INTERCEPTORS, useClass: CharacterServiceInterceptor, multi: true} ], bootstrap: [AppComponent] }) export class AppModule { }
This second mocking approach is way better than the previous one as it allows having the service as close as possible to its final version, while pretending the responses are coming from a real back-end.
The source code for this stage is here – https://github.com/horatiucd/ninjago-front-end/tree/v3.0.0.
Mock using a HttpXhrBackend mock
In order to trigger HTTP calls from Angular, usually a real
endpoint is needed – the previously referred back-end. Since
Angular has a powerful dependency injection engine and the codebase
is constructed upon it, this can be leveraged to replace the
HttpXhrBackend
class that handles the HTTP calls and
set-up a mock for it to simulate these.
In order to accomplish this, the HttpBackend
class
is extended. According to the documentation, this
HttpHandler
dispatches the requests via browser HTTP
APIs to a backend. Interceptors sit between the
HttpClient
interface and the HttpBackend
.
When injected, the HttpBackend
dispatches requests
directly to the backend, without going through the interceptor
chain.
As part of the /mocks
directory,
MockHttpBackend
is added –
mock-http-backend.ts
HttpBackend
has one abstract method
handle(req: HttpRequest<any>): Observable<HttpEvent<any>>
that returns an Observable
and takes the
HttpRequest
as argument.
The implementation is similar to the one used in the
interceptor. In case of GET requests towards
/api/v1/characters
, an observable containing a HTTP
200 status and the CHARACTERS
as body is returned.
import {HttpBackend, HttpEvent, HttpRequest, HttpResponse} from '@angular/common/http'; import {Observable} from 'rxjs'; import {Character} from '../character/character'; import {CHARACTERS} from './characters'; export class MockHttpBackend implements HttpBackend { private readonly GET_CHARACTERS_URL = '/api/v1/characters'; handle(req: HttpRequest<any>): Observable<HttpEvent<any>> { if (req.url === this.GET_CHARACTERS_URL && req.method === 'GET') { return this.getCharacters(); } } private getCharacters(): Observable<HttpResponse<Character[]>> { return new Observable<HttpResponse<Character[]>>(observer => { observer.next(new HttpResponse<Character[]>({ status: 200, body: CHARACTERS })); observer.complete(); }); } }
This allows building the CharacterService
to use
the Angular HTTP service and not require a separate API to call.
Basically, by leveraging Angular DI architecture, the default
HttpXhrBackend
class can be replaced with the mock
class.
In app.module.ts
, both HttpXhrBackend
and MockHttpBackend
(the mocked type) need to be
imported and also registered in the providers array of the
@NgModule
decorator.
import {HttpClientModule, HttpXhrBackend} from '@angular/common/http'; import {MockHttpBackend} from './mocks/character-service-mock-backend'; @NgModule({ declarations: [ AppComponent, CharacterListComponent, CharacterComponent ], imports: [ BrowserModule, AppRoutingModule, HttpClientModule ], providers: [ { provide: HttpXhrBackend, useClass: MockHttpBackend } ], bootstrap: [AppComponent] }) export class AppModule { }
Basically, when a HttpXhrBackend
is needed, an
instance of a MockHttpBackend
is used.
The source code for this stage is here – https://github.com/horatiucd/ninjago-front-end/tree/v4.0.0
In my opinion, the 3rd option is the most convenient one, as it
allows having the services that use the HttpClient
in
their final form and under the hood, the calls to the backend are
responded from the mocked HttpXhrBackend
.
Once a real back-end is available, it is very easy to switch and
use it instead.