guide consuming rest services - devonfw/devon4ng GitHub Wiki
A good introduction to working with Angular HttpClient
can be found in Angular Docs
This guide will cover, how to embed Angular HttpClient
in the application architecture.
For back-end request a special service with the suffix Adapter
needs to be defined.
It is a good practice to have a Angular service whose single responsibility is to call the back-end and parse the received value to a transfer data model (e.g. Swagger generated TOs).
Those services need to have the suffix Adapter
to make them easy to recognize.
As illustrated in the figure a Use Case service does not use Angular HttpClient
directly but uses an adapter.
A basic adapter could look like this:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { FlightTo } from './flight-to';
@Injectable({
providedIn: 'root',
})
export class FlightsAdapter {
constructor(
private httpClient: HttpClient
) {}
getFlights(): Observable<FlightTo> {
return this.httpClient.get<FlightTo>('/relative/url/to/flights');
}
}
The adapters should use a well-defined transfer data model. This could be generated from server endpoints with CobiGen, Swagger, typescript-maven-plugin, etc. If inside the application there is a business model defined, the adapter has to parse to the transfer model. This is illustrated in the following listing.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { map } from 'rxjs/operators';
import { FlightTo } from './flight-to';
import { Flight } from '../../../model/flight';
@Injectable({
providedIn: 'root',
})
export class FlightsAdapter {
constructor(
private httpClient: HttpClient
) {}
updateFlight(flight: Flight): Observable<Flight> {
const to = this.mapFlight(flight);
return this.httpClient.post<FlightTo>('/relative/url/to/flights', to).pipe(
map(to => this.mapFlightTo(to))
);
}
private mapFlight(flight: Flight): FlightTo {
// mapping logic
}
private mapFlightTo(flightTo: FlightTo): Flight {
// mapping logic
}
}
In most cases the access to back-end API is secured using well known mechanisms as CSRF, JWT or both. In these cases the front-end application must manage the tokens that are generated when the user authenticates. More concretely it must store them to include them in every request automatically. Obviously, when user logs out these tokens must be removed from localStorage
, memory, etc.
In order to make this guide simple we are going to store the token in memory. Therefore, if we consider that we already have a login mechanism implemented we would like to store the token using a auth.service.ts
:
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
@Injectable({
providedIn: 'root',
})
export class AuthService {
private loggedIn = false;
private token: string;
constructor(public router: Router) {}
public isLogged(): boolean {
return this.loggedIn || false;
}
public setLogged(login: boolean): void {
this.loggedIn = login;
}
public getToken(): string {
return this.token;
}
public setToken(token: string): void {
this.token = token;
}
}
Using the previous service we will be able to store the token obtained in the login request using the method setToken(token)
. Please consider that, if you want a more sophisticated approach using localStorage
API, you will need to modify this service accordingly.
Now that the token is available in the application it is necessary to include it in every request to a protected API endpoint. Instead of modifying all the HTTP requests in our application, Angular provides a class to intercept every request (and every response if we need to) called HttpInterceptor
. Let’s create a service called http-interceptor.service.ts
to implement the intercept
method of this class:
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from '../../../environments/environment';
import { AuthService } from './auth.service';
@Injectable()
export class HttpRequestInterceptorService implements HttpInterceptor {
constructor(private auth: AuthService) {}
intercept(
req: HttpRequest<any>,
next: HttpHandler,
): Observable<HttpEvent<any>> {
// Get the auth header from the service.
const authHeader: string = this.auth.getToken();
if (authHeader) {
let authReq: HttpRequest<any>;
// CSRF
if (environment.security === 'csrf') {
authReq = req.clone({
withCredentials: true,
setHeaders: { 'x-csrf-token': authHeader },
});
}
// JWT
if (environment.security === 'jwt') {
authReq = req.clone({
setHeaders: { Authorization: authHeader },
});
}
return next.handle(authReq);
} else {
return next.handle(req);
}
}
}
As you may notice, this service is making use of an environment field environment.security
to determine if we are using JWT or CSRF in order to inject the token accordingly. In your application you can combine both if necessary.
Configure environment.ts file to use the CSRF/JWT.
security: 'csrf'
The authHeader
used is obtained using the injected service AuthService
already presented above.
In order to activate the interceptor we need to provide it in our app.module.ts
or core.module.ts
depending on the application structure. Let’s assume that we are using the latter and the interceptor file is inside a security
folder:
...
import { HttpRequestInterceptorService } from './security/http-request-interceptor.service';
...
@NgModule({
imports: [...],
exports: [...],
declarations: [],
providers: [
...
{
provide: HTTP_INTERCEPTORS,
useClass: HttpRequestInterceptorService,
multi: true,
},
],
})
export class CoreModule {}
Angular automatically will now modify every request and include in the header the token if it is convenient.