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.