It is time to update my original Angular REST 4 example to an Angular 6 REST API Example. We will start with a fully functional Hello- World-like implementation before we start refactoring the service, interfaces and interceptors into separate files. Similar to the latest 6.0 Tour of Heros, we will make use of pipes.
Phase 1+2 of this post is covering most of the original post, enriched with troubleshooting steps via the Chrome browser. The refactoring chapter (‚Phase 3‘) focuses on increasing the readability, maintainability, and testability of the code by separating the service into its own file, and moving API specifics from component to the service file.
Introduction
What does the User get?
Within a few steps and minimal effort, we will display the user with a list of WordPress blog posts. Moreover, we will display the content of a single post, after the user clicks an entry of the list:
Initial Architecture: REST API Hello World
The initial architecture will be very simple:
We just will add a HttpClientModule for contacting the WordPress REST API. The component will transform the retrieved data for proper display in the Browser.
Target Architecture: Maintainable REST Example
Without any change in the functionality, we will perform a few refactoring steps in order to increase the maintainability of the code. We will move the HttpClientModule and transformation code into a separate service in its own file. In addition, we will demonstrate how to add headers on outgoing HTTP request using an interceptor. Moreover, we will demonstrate how the interceptor can be used to perform simple normalization transformations on incoming HTTP responses.
In the course of the refactoring session, we will show, how to maintain subscriptions while avoiding memory leaks by keeping track of the subscriptions or, better, by using async pipes within the HTML template.
In detailed view, this looks like the following:
The HTML template will subscribe and view the list of posts. It will retrieve the data from the component. For getting the posts data, the component calls the HttpClient Module get function, which calls the WordPress REST API. An interceptor will add HTTP headers on outgoing requests and it will normalize the data received, so the service can rely on receiving a list of posts. The service will transform the response by labeling the HTML code within the response as trusted. Last, but not least, the component will rextract the transformed data and assign it to the posts.
This way, the title and the content of the posts is displayed in the browser.
How to download the Angular REST API Example
You can download the most important code points by cloning the Angular REST API Example GIT Repo and checking out the branch or tag:
git clone https://github.com/oveits/consuming-a-restful-web-service-with-angular-vi git checkout <branch or tag>
The GIT repo has following feature branches or tags, currently:
- feature/0001-refactor-service-in-own-file
- feature/0002-add-unsubscribe
- feature/0003-add-async-pipes
- feature/0004-move-safeHTML-to-component
- feature/0005-move-safeHTML-to-service
- feature/0006-move-safeHTML-to-service-with-unsubscribes
- feature/0007-move-safeHTML-to-service-with-async-pipes
- feature/0008-add-wordpress-interceptor
- feature/0009-refresh-via-interval-using-subscribes
- feature/0010-refresh-via-interval-using-async-pipes-but-bad-user-experience
- feature/0011-refresh-via-interval-using-async-pipes-fixed-via-class-variable-assignment
- feature/0012-refresh-via-intervalBackoff
- feature/0013-refresh-via-intervalBackoff-using-double-async-pipes
- feature/0014-refresh-via-intervalBackoff-using-switchmap
Change History
- 2018-08-05
- exponential backoff with async pipes
- 2018-08-02
- we have added an introduction with a description, how the architecture will evolve during the course of the refactoring steps
- 2018-07-30
- cleaned the GIT repo from obsolete branches; renamed branches; added download instructions
- 2018-07-28
- Separate Service File: reworked
- Hide API specifics from the HTML template: reworked
- Hide API specifics from the Component: reworked
- Managing Subscriptions: reworked
- 2018-07-27
- Managing Subscriptions through async pipes: new
- 2018-05-12
- tested with and adapted to Angular 6.
- 2018-04-06
- Updated from deprecated HttpModule (@angular/http) to new HttpClientModule (@angular/common/http)
- 2018-03-24
- tested with Angular 5.2
- included information about how to retain the full HTML functionality of the content retrieved via the REST API
Phase 1: Install Angular Hello World App in a Docker Container
We will install an Angular hello world application in a CentOS Docker container. You may skip the Docker part and can perform the same steps on a real CentOS server if you wish.
Step 1.1 (optional): Run a CentOS Docker Container
mkdir angular-on-centos; cd angular-on-centos docker run -it -p 4200:4200 -v $(pwd):/localdir centos bash
Step 1.2: Install NodeJS 10.x and Angular CLI
A recent v10.x version of NodeJS can be installed with the following command (see https://nodejs.org/en/download/package-manager/ for details):
(container)# curl --silent --location https://rpm.nodesource.com/setup_10.x | bash - && yum install -y nodejs
Note: the standard nodejs installation way via EPEL_Release (
yum install -y epel-release && yum install -y nodejs
) will install a too old version of Nodejs, currently
Now is the time to install Angular CLI:
(container)# npm install -g @angular/cli@6.0.0
Now let us verify the version of angular and its components:
(container)# ng -v
_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ ? \ | '_ \ / _` | | | | |/ _` | '__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/
Angular CLI: 6.0.0
Node: 10.1.0
OS: linux x64
Angular:
...
Package Version
------------------------------------------------------
@angular-devkit/architect 0.6.0
@angular-devkit/core 0.6.0
@angular-devkit/schematics 0.6.0
@schematics/angular 0.6.0
@schematics/update 0.6.0
rxjs 6.1.0
typescript 2.7.2
Okay, at the moment, Angular CLI 6.0.0 is the standard version.
Step 1.3 (optional): Install GIT
You may want to keep track of the changes you perform on the source code. For that, we can install GIT as follows:
curl https://raw.githubusercontent.com/oveits/bootstrap-centos/master/2_update-git-centos.sh | bash
With that, a relatively recent version of GIT is installed
Note: here again, the CentOS standard installation via EPEL-Release (
yum install -y epel-release && yum install -y git
) leads to a quite old installed version and not nice to work with.
Step 1.3: Create new Project
Step 1.3.1: Create the Project
Now let us create a project:
cd /localdir ng new my-project-name cd my-project-name
Depending on the speed of your Internet connection, the second command might take some minutes.
Step 1.3.2 Check the Version of the Project
$ grep @angular/core package.json "@angular/core": "^6.0.0",
We can see that angular has generated a v6.0 Angular project in this case.
Step 1.4: Start the Service
With the following command, we start the service:
$ ng serve --host 0.0.0.0 --port=4200
The host option is needed if you access the service from outside. The port option is not needed since the default port is 4200.
Step 1.5: Check the App in a Browser
Let us open a browser and head over to http:<host>:4200. In your case, the host might be ‚localhost‘. Since I was running the application on a cloud server, I had to enter the IP-Address of the server:
Note: you may want to initialize GIT at this point in order to track further changes:
cd <project-root> git config --global user.email "you@example.com" git config --global user.name "Your Name" git init git add . git commit -am'initial commit after ng new'
Phase 2: Programming the Angular 6 REST API Client
In this phase, we will see, how easy it is in Angular 4.3+ to 6 to retrieve and display data from a REST API.
Step 2.1: Add HTTP Client Module
First, we need to tell Angular that we will use the HttpClientModule. For that, we edit src/app/app.module.ts (added parts in blue).
Note: since the HttpModule is deprecated now, we have replaced the module by the more modern HttpClientModule.
Now let us begin to edit the files (new parts in blue):
src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ HttpClientModule, BrowserModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
With that, we have told Angular, that we will make use of the HttpClientModule (requires Angular 4.3+).
src/app/app.component.ts
import { Component, OnInit } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { map } from 'rxjs/operators'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'app'; restItems: any; restItemsUrl = 'https://public-api.wordpress.com/rest/v1.1/sites/vocon-it.com/posts'; constructor(private http: HttpClient) {} ngOnInit() { this.getRestItems(); } // Read all REST Items getRestItems(): void { this.restItemsServiceGetRestItems() .subscribe( restItems => { this.restItems = restItems; console.log(this.restItems); } ) } // Rest Items Service: Read all REST Items restItemsServiceGetRestItems() { return this.http .get<any[]>(this.restItemsUrl) .pipe(map(data => data)); } }
Here we have used the more modern pipe(map…) way of implementing the service. Moreover, we have separated the actual service into its own function, so it is easier to separate this part into its own service file later.
The OnInit() function will call the service upon reload of the page and we expect that an HTTP GET call is sent to the REST Service. We will explore this in the next step.
Step 2.2 (optional): View the REST call in the Browser’s Network Panel
The REST call and its answer can be seen in the network panel that is available in the debug mode of a Chrome browser (press F12):
The content of the response body is sent to the console via the console.log call in the subscribe method. Because of pagination on the WordPress REST API, we can see the latest 20 of the 72 public posts of this blog:
We also can see that the REST API has provided us with the title and content of all of the 20 posts:
We now want to make the content visible.
Step 2.3: Display the List of Posts in the Browser
Now let us display the blog posts. We remove all content of src/app/app.component.html and replace it with the following content:
<h1>Blog Posts</h1> <div *ngIf="undefined === restItems">Loading...</div> <div *ngIf="undefined !== restItems"> <ul> <li *ngFor="let post of restItems['posts']" [innerHTML]="post.title"> </li> </ul> </div>
Within the HTML template, we loop through the array of posts that can be found in the ‚posts‘ section of the REST response. With the *ngIf statements, we make sure that we do not try to read ‚posts‘ from restItems before they have not yet been read successfully from the API. Moreover, we show a „Loading…“ information while waiting for the data.
In the HTML template, we have chosen an innerHTML binding, so the HTML content of the titles is displayed correctly:
This is goo, but let us add some more functionality:
Step 2.4: Display a single Post Content in the Browser
<h1>Blog Posts</h1> <div *ngIf="undefined === restItems">Loading...</div> <div *ngIf="undefined !== restItems"> <ul> <li *ngFor="let post of restItems['posts']" [innerHTML]="post.title" (click)="selectedContent='<h1>' + post.title + '</h1>' + post.content"> </li> </ul> </div> <div [innerHTML]="selectedContent"></div>
With that, we the content of the post is shown at the bottom after clicking the entry:
Step 2.5: Fix the Table of Contents
My WordPress page will return a table of contents (TOC) because I have installed a TOC plugin. However, you will note that the links to the headlines do not work. We will see the reason in the debug console (press F12) of the Chrome browser:
WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
The problem and the workaround is described in my blog post „Angular 4: Automatic Table of Contents – Part 2: Adding Links„. However, the fix can be also seen in the listing of chapter 3.2.3 or better 3.3 below.
My WordPress page will return a table of contents (TOC) because I have installed TOC plugin. However, you will note that the links to the headlines do not work. We will see the reason in the debug console (press F12) of the Chrome browser:
WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
The problem and the workaround is described in my blog post “Angular 4: Automatic Table of Contents – Part 2: Adding Links“.
A quick&dirty way of resolving the problem is to add/replace following lines in ‘src/app/app.component.html’:
... import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; ... constructor(private http: HttpClient, public sanitizer: DomSanitizer) {}
After that, we only need to replace
<div [innerHTML]="selectedContent"></div>
by
<div *ngIf="undefined !== selectedContent" [innerHTML]="sanitizer.bypassSecurityTrustHtml(selectedContent)"></div>
After that, the links in the HTML Content work fine. After clicking one of the table of contents links, we reach at the correct headline:
This works fine and we have reached a quite simple architecture:
We just have added a HttpClientModule for contacting the WordPress REST API. The component also transforms the response by labeling the HTML code retrieved as „trusted“. With that, the browser dares to use the HTML code as it is, which helps us make the links therein fully functional.
Step 3 (optional): Refactoring (1): Move Angular REST API Service to its own File
In the steps above, we have taken a shortcut to a working solution. This is good to get early results. However, we have put almost all code into the main app.component.ts file. When we proceed this way, the code will become unreadable and unmaintainable.
At this early stage, let us start with some refactoring steps. We will
- move functionality from HTML template to the component file
- move the service part into a separate file
- get rid of subscriptions in the component and let the HTML template handle them using the „async“ keyword
Step 3.1: Create Separate Service File
For increasing the re-usability, it makes sense to separate the service into its own file. For that, we add a service.ts file as follows:
src/app/app.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Injectable()
export class AppService implements OnInit {
protected url : string = 'https://public-api.wordpress.com/rest/v1.1/sites/vocon-it.com/posts';
constructor(private http: HttpClient, public sanitizer: DomSanitizer) {}
// Rest Items Service: Read all REST Items
getAll() {
return this.http
.get<any[]>(this.url)
.pipe(map(data => data));
}
}
The corresponding code can be stripped from the component file:
src/app/app.component.ts
import { Component, OnInit } from '@angular/core';import { HttpClient } from '@angular/common/http'; import { map } from 'rxjs/operators';import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { AppService } from './app.service.ts'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'app'; restItems: any; url = 'https://public-api.wordpress.com/rest/v1.1/sites/vocon-it.com/posts'; constructor(private http: HttpClient) {} ngOnInit() { this.getRestItems(); } // Read all REST Items getRestItems(): void { this.appService.getAll() .subscribe( restItems => { this.restItems = restItems; console.log(this.restItems); } ) }// Rest Items Service: Read all REST Items restItemsServiceGetRestItems() { return this.http .get<any[]>(this.restItemsUrl) .pipe(map(data => data)); }}
Now we need to let angular know that a new service provider is present:
src/app/module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; import { AppComponent } from './app.component'; import { AppService } from './app.service'; @NgModule({ declarations: [ AppComponent ], imports: [ HttpClientModule, BrowserModule ], providers: [ AppService ], bootstrap: [AppComponent] }) export class AppModule { }
The changes should lead to an unchanged output:
Nothing has changed, but we now have a cleaner separation of the service from the rest of the code.
Step 4 (optional): Refactoring (2): Hide Angular REST API specifics from the HTML Template
The HTML template has an element that is specific to the API we are using: The API answers with data that is structured as follows:
{ "posts": [ (list of posts) ] }
This is reflected in the HTML template:
<li *ngFor="let post of restItems['posts']" [innerHTML]="post.title"
We extract the list of posts by reading the single posts element of the data.
The task of this step is to move API specifics from the view template to the service (before we move it even closer to the API by moving it to the interception).
Step 4.1: Change HTML Template
What we want to achieve is, that the HTML template is working with a list of posts directly:
src/app/app.component.html
<h1>Blog Posts</h1> <div *ngIf="undefined === posts">Loading...</div> <ul> <li *ngFor="let post of posts" [innerHTML]="post.title" (click)="selectedPost = post"> </li> </ul> <div *ngIf="selectedPost"> <h1 [innerHTML]="selectedPost.title"></h1> <div [innerHTML]="selectedPost.content"></div> </div>
Here, we have exchanged restItems['posts']
by a list of posts
. With this, we do not care anymore, whether or not the API returns { "posts": [(list of posts)]}
or [(list of ports)]
or whatever.
Before this works, we need to change the component and the service, which we will do now:
Step 4.2: Allow Service to extract Posts
We now simply extract the posts element in the service, before we hand it over to the component. This is a little change, where we map the received data
to the 'posts'
element of the received data (data['posts']
):
src/app/app.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
@Injectable()
export class AppService {
protected url : string = 'https://public-api.wordpress.com/rest/v1.1/sites/vocon-it.com/posts';
constructor(private http: HttpClient) {}
// Rest Items Service: Read all REST Items
getAll() {
return this.http
.get<any[]>(this.url)
.pipe(map(data => data['posts']));
}
}
Step 4.3: Bind it together: Component reads List of Posts from Service
In the component file, we need to define the list of posts:
src/app/app.component.html
import { Component, OnInit } from '@angular/core'; import { map } from 'rxjs/operators'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { AppService } from './app.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { posts: any; constructor(private appService : AppService, public sanitizer: DomSanitizer) {} ngOnInit() { this.getRestItems(); } // Read all REST Items getRestItems(): void { this.appService.getAll() .subscribe( allPostsData => { this.posts = allPostsData.map( singlePostData => { return { "title": this.sanitizer.bypassSecurityTrustHtml(singlePostData.title), "content": this.sanitizer.bypassSecurityTrustHtml(singlePostData.content) } }); console.log(this.posts); } ) } }
While the result is not changed at all, we have moved typescript functionality from HTML template the the typescript files, where it belongs to (we might find better places to move that code to, though).
Step 5 (optional): Refactoring (2): Hide Angular REST API specifics from the Component
Since we have handled the safe HTML translation in the component, it has grown quite complex, with its nested maps. We now could separate the safe HTML code to a separate function, but we choose to go even further and separate this functionality into the service. There, we will use interfaces and functions for better maintainability.
Our target is to get a component that is as simple as this:
src/app/app.component.html
import { Component, OnInit } from '@angular/core'; import { AppService } from './app.service'; import { SafePost } from './safe-post.interface'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { posts: SafePost[]; constructor(private appService: AppService) {} ngOnInit() { this.getRestItems(); } // Read all REST Items getRestItems(): void { this.appService.getAll() .subscribe(posts => { this.posts = posts; }); } }
The component is clean an generic.
Here, we have assumed that the appService is returning an array of SafePost in a sanitized format. The format of SafePost is defined in a new, separate interface file:
src/app/safe-post.interface.ts
import { SafeHtml } from '@angular/platform-browser';
export interface SafePost {
'title': SafeHtml;
'content': SafeHtml;
}
Now, the heavy lifting will be done by the appService:
src/app/app.service.ts
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { map } from 'rxjs/operators'; import { DomSanitizer } from '@angular/platform-browser'; import { SafePost } from './safe-post.interface'; @Injectable() export class AppService { protected url = 'https://public-api.wordpress.com/rest/v1.1/sites/vocon-it.com/posts'; constructor(private http: HttpClient, public sanitizer: DomSanitizer) {} safePost(apiDataSinglePost: APIDataSinglePost): SafePost { return { 'title': this.sanitizer.bypassSecurityTrustHtml(apiDataSinglePost.title), 'content': this.sanitizer.bypassSecurityTrustHtml(apiDataSinglePost.content) }; } // Rest Items Service: Read all REST Items getAll() { return this.http .get<any[]>(this.url) .pipe(map(apiDataAllPosts => { const mySafePosts: SafePost[] = apiDataAllPosts['posts'] .map(apiDataSinglePost => this.safePost(apiDataSinglePost)); console.log(mySafePosts); return mySafePosts; })); } } interface APIDataSinglePost { 'title': string; 'content': string; }
Explanation
Here, we first have mapped the apiAllPosts data of the format {‚posts‘: […]} to a simple array […]:
map(apiDataAllPosts => {
const mySafePosts: SafePost[] = apiDataAllPosts['posts']
...
Each entry of the array is transformed by the second map. Each apiDataSinglePost is replaced by a SafePost returned by the safePost function:
.map(apiDataSinglePost => this.safePost(apiDataSinglePost));
The safePost function, in turn, performs the actual transformation from strings to SafeHTMLs:
safePost(apiDataSinglePost: APIDataSinglePost): SafePost {
return {
'title': this.sanitizer.bypassSecurityTrustHtml(apiDataSinglePost.title),
'content': this.sanitizer.bypassSecurityTrustHtml(apiDataSinglePost.content)
};
}
Altogether, we have succeeded in simplifying the component by moving API specifics to the service, where they belong to.
Step 6 (mandatory): Managing Subscriptions
Note, that this chapter starts from a code we had created in
Houston, we have a memory leak!
You may or may not have noticed: we have created a memory leak in the code above. We have created subscriptions in the component file, but we have missed unsubscribing at the time the component is closed. We now have two options to handle this:
- the straightforward way: memorize the subscription and unsubscribe, when closing the component
- the clean way: get rid of the subscriptions in the component file and let the HTML template handle this
Step 6.1: Straightforward Option: Unsubscribe the Service on Destroy
This way of getting rid of the subscription memory leak is quite straightforward: we
- create a private variable named ’subscription‘ and
- memorize the subscription until the time the component is not needed anymore:
- we unsubscribe at the end.
The relevant changes in the component file are marked in blue:
src/app/app.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; import { AppService } from './app.service'; import { SafePost } from './safe-post.interface'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, OnDestroy { posts: SafePost[]; private subscription: Subscription; constructor(private appService: AppService) {} ngOnInit() { this.subscription = this.getRestItems(); } // Read all REST Items getRestItems(): Subscription { return this.appService.getAll() .subscribe(posts => { this.posts = posts; }); } ngOnDestroy() { this.subscription.unsubscribe(); } }
With this, the memory leak should be fixed. However, there is a better method described in the next section.
Step 6.2: Clean Option: Replace Subscriptions by Async Pipes
In this section, we will follow a hint of this article on alligator.io and we will get rid of any static subscriptions in the component file.
Why Async Pipes instead of Component-managed Subscriptions?
This is (slightly) more effort than the straightforward option above. So, why did I chose it nevertheless?
The reason is, that I already have made a bad experience with component-managed subscriptions: I have had a case, where a function has replaced one subscription by another. First, I had only one subscription variable like above, and I made the function remove the old subscription before creating a new one. Even though the subscription was released also in ngOnDestroy, sometimes a subscription was still active when I routed from one page to the other.
I have managed the problem using arrays of subscription variables. However, I have noticed that I found myself in subscription management hell. A solution without component-management subscriptions as described here is much easier to maintain.
Step 6.2.1 Getting rid of Component Managed Subscriptions
As a first step, we replace the component managed subscription by a pipe
import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { AppService } from './app.service'; import { SafePost } from './safe-post.interface'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { posts$: Observable<SafePost[]>; constructor(private appService: AppService) {} ngOnInit() { this.posts$ = this.getRestItems$(); } // Read all REST Items getRestItems$(): Observable<SafePost[]> { return this.appService.getAll() .pipe(posts => posts); }
Here, we have followed the convention to append a single $ sign for Observables in Anglar (for the function as well as for the variable).
We have changed the nature of getRestItems$() function from Subscription to an Observable. At startup of the component, we assign the return value of getRestItems$() to our posts$ variable.
At this point, we will see, that the posts are not loading successfully in the browser:
This is because the subscriptions are missing. Moreover, the HTML template has not yet been adopted, which we will do now:
Step 6.2.2: Adding async Keyword to the HTML Template
We now need to adapt the HTML template to the changes we have made above. For one thing, the variable name has changed from posts to posts$. More importantly, we need to subscribe to the observable posts$ by piping it to async:
<h1>Blog Posts</h1> <div *ngIf="null === (posts$ | async)">Loading...</div> <ul> <li *ngFor="let post of (posts$ | async)" [innerHTML]="post.title" (click)="selectedPost = post"> </li> </ul> <div *ngIf="selectedPost"> <h1 [innerHTML]="selectedPost.title"></h1> <div [innerHTML]="selectedPost.content"></div> </div>
Now, the browser succeeds in loading the blog posts again:
For the ‚Loading…‘ hint to appear during loading, I had to change the
undefined === posts
tonull === (posts$ | async)
. Neither the variableposts$
nor the variable(posts$ | async)
ever seem to be undefined.
Okay, now we have refactored the code, and it is still working. Enough for today.
😉
To download Code via GIT, perform the following to commands:
git clone https://github.com/oveits/consuming-a-restful-web-service-with-angular-iv git checkout feature/0005-move-safeHTML-to-service-with-async-pipes
Step 7 (optional): Angular REST API Interceptors
Interceptors are a new cool feature of the HttpClientModule (available since Angular 4.3). With an interceptor, we can hide the REST API specifics into a REST API specific interceptor file. As most tutorials do, let us begin with a NOOP interceptor that does not do anything:
Step 7.1: Create Interceptor File
As most tutorials do (e.g. on angular.io), let us begin with a NOOP interceptor that does not do anything. For that, let us create following file:
src/app/wordpress-api.interceptor.ts
import { Injectable } from '@angular/core';
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpEvent,
HttpResponse
} from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class WordPressApiInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest,
next: HttpHandler
): Observable<HttpEvent> {
console.log('WordpressApiInterceptor was called');
return next.handle(request);
}
}
As described in the angular.io documentation, each interceptor needs to define an intercept() function, which returns a next.handle(request).
Step 7.2: Activate Interceptor
The interceptor needs to be activated in the App Module:
src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { WordPressApiInterceptor } from './wordpress-api.interceptor'; import { AppComponent } from './app.component'; import { AppService } from './app.service'; @NgModule({ declarations: [ AppComponent ], imports: [ HttpClientModule, BrowserModule ], providers: [ AppService, { provide: HTTP_INTERCEPTORS, useClass: WordPressApiInterceptor, multi: true } ], bootstrap: [AppComponent] }) export class AppModule { }
The ‘multi: true’ is needed, because HTTP_INTERCEPTORS is an array of values.
Step 7.3: Verify that the Interceptor is called
We had added a console log in the interceptor, that now can be observed in the console of a chrome browser after pressing F12:
Moreover, we an debug the code by navigating to Webpack –> . –> src –> app –> wordpress-api.interceptor.ts and setting a breakpoint:
Step 7.4: Adapt the Interceptor for outgoing Requests
In this section, we will show how to add a custom header in outgoing requests.
Note: in the pre-flight OPTION, WordPress tells me that they accept only following headers: Authorization, Content-Type, X-Jetpack. Let us manipulate the Content-Type for this hello-world…
We can add an authentication header like follows:
src/app/wordpress-api.interceptor.ts
import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable() export class WordPressApiInterceptor implements HttpInterceptor { intercept( request: HttpRequest, next: HttpHandler ): Observable<HttpEvent> { console.log('WordpressApiInterceptor was called'); const clonedRequest = request.clone({ headers: request.headers.set('Content-Type', 'BLABLUB') }); return next.handle(clonedRequest); } }
Note: requests are read-only, so we need to clone the original request and hand over the clone via next.handle instead of changing the request. We are choosing an easily recognizable BLABLUB Content-Type. Next time a package is sent to the API, the header appears in the network panel of the browser’s debug mode (F12):
This shows, how we can manipulate headers in outgoing requests.
Step 7.5: Manipulate incoming Responses
In this section, we will learn how to manipulate the bodies on incoming responses.
Above, we have seen that the WordPress API is returning an answer of the format
{ posts: [ ... ] }
To extract the posts, we had (more or less) used following map in the service:
map(apiDataAllPosts => apiDataAllPosts['posts']
The target of this section is to move this interface specific manipulation to the interceptor. For that, we replace the following line in the interceptor file:
src/app/wordpress-api.interceptor.ts
return next.handle(clonedRequest);return next.handle(clonedRequest).pipe(map(event => { if (request.method === 'GET' && event instanceof HttpResponse) { console.log("Response Interceptor for GET"); return event.clone({ body: event.body['posts']}); } }));
Note: The if-clause is mandatory for making sure the body is manipulated on GET responses only. If the if-clause was missing here, the interceptor would try to manipulate the Http Packet for outgoing requests as well.
We now can simplify the service by removing the posts extraction, since this work is now done by the interceptor:
src/app/app.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { DomSanitizer } from '@angular/platform-browser';
import { SafePost } from './safe-post.interface';
@Injectable()
export class AppService {
protected url = 'https://public-api.wordpress.com/rest/v1.1/sites/vocon-it.com/posts';
constructor(private http: HttpClient, public sanitizer: DomSanitizer) {}
safePost(apiDataSinglePost: APIDataSinglePost): SafePost {
return {
'title': this.sanitizer.bypassSecurityTrustHtml(apiDataSinglePost.title),
'content': this.sanitizer.bypassSecurityTrustHtml(apiDataSinglePost.content)
};
}
// Rest Items Service: Read all REST Items
getAll() {
return this.http
.get<any[]>(this.url)
.pipe(map(apiDataAllPosts => {
const mySafePosts: SafePost[] = apiDataAllPosts['posts']
.map(apiDataSinglePost => this.safePost(apiDataSinglePost));
console.log(mySafePosts);
return mySafePosts;
}));
}
}
interface APIDataSinglePost {
'title': string;
'content': string;
}
With that, the posts are loaded successfully again:
As we have seen above, the interceptor code is more complex than the code we were able to remove from the service, but it helps to keep service clean from any API-specifics, making the service code re-usable for other APIs.
Note: Architecture after Refactoring
With that, we have reached the point, where the architecture looks consists of the following elements:
Without any change in the functionality, we have performed a few refactoring steps in order to increase the maintainability of the code. We have moved the HttpClientModule and transformation code into a separate service in its own file. In addition, we have demonstrated how to add headers on outgoing HTTP request using an interceptor. Moreover, we have demonstrated how the interceptor can be used to perform simple normalization transformations on incoming HTTP responses.
In the course of the refactoring session, we also have shown, how to maintain subscriptions while avoiding memory leaks by keeping track of the subscriptions or, better, by using async pipes within the HTML template.
In detailed view, this looks like the following:
The HTML template subscribes and views the list of posts. It will retrieve the data from the component. For receiving the posts data, the component calls the HttpClient Module GET function, which calls the WordPress REST API. An interceptor adds HTTP headers on outgoing requests and it normalizes the data received, so the service can rely on receiving a list of posts. The service transforms the response by labeling the HTML code within the response as trusted. Last, but not least, the component extracts the transformed data and assigns it to the posts.
This way, the title and the content of the posts are displayed in the browser.
Step 8: Periodic Refresh
We are consuming data from an external API, by reading the data once and displaying it forever. What, if the data changes? In this case, we want the change to be reflected in our browser as well. In this chapter, we investigate, how to refresh data. We will take three attempts, actually:
- first, we will show the quickest way to a periodic refreshing data using a double subscribe model
- then we will argue that this patter can lead to memory leaks
- but we will show, how to avoid memory leaks by implementing unsubscribes
- then we will show a cleaner way using async pipes, which will lead to a blinking behavior in a first attempt
- as the good interval solution, we will promote using a combination of async pipes calling void functions that re-assign a class variable
- finally, we will test a promising the exponential backoff solution found in this article.
Step 8.1 (optional): Quick&Dirty Interval Way with double Subscriptions
In this chapter, we will show a Hello World style way of refreshing the displayed data. We just use interval() and subscribe to it, and within the subscriptions, we subscribe to our service:
src/app/app.component.ts
import { Component, OnInit } from '@angular/core'; import { AppService } from './app.service'; import { SafePost } from './safe-post.interface'; import { interval} from 'rxjs'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { posts: SafePost[]; constructor(private appService: AppService) {} ngOnInit() { interval(10000).subscribe((counter) => { this.appService.getAll().subscribe(posts => { this.posts = posts; }); console.log(counter + ': read restItems'); }); } }
In the HTML template, we need to loop over the posts array:
src/app/app.component.html
<h1>Blog Posts</h1> <div *ngIf="undefined === posts">Loading...</div> <ul> <li *ngFor="let post of posts" [innerHTML]="post.title" (click)="selectedPost = post"> </li> </ul> <div *ngIf="selectedPost"> <h1 [innerHTML]="selectedPost.title"></h1> <div [innerHTML]="selectedPost.content"></div> </div>
We can see in the Browser debug console (press F12) that the API is contacted every 10 sec:
Step 8.2: Fix the Memory Leaks
Houston, we got a memory leak (again)!
As in a previous chapter, we just have created memory leaks by creating subscriptions without unsubscribes. As soon, as we navigate to another route (which is not implemented here, though), we will see that the interval will run more than once.
Let us repair the problem by adding unsubscribes:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { AppService } from './app.service'; import { SafePost } from './safe-post.interface'; import { interval, Subscription} from 'rxjs'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, OnDestroy { posts: SafePost[]; subInner: Subscription; subOuter: Subscription; constructor(private appService: AppService) {} ngOnInit() { this.subOuter = interval(10000).subscribe((counter) => { if (this.subInner !== undefined) { console.log('unsubscribing subInner'); this.subInner.unsubscribe(); } this.subInner = this.appService.getAll().subscribe(posts => { this.posts = posts; }); console.log(counter + ': read restItems'); }); } ngOnDestroy() { this.subInner.unsubscribe(); this.subOuter.unsubscribe(); } }
We can see, that unsubscribing requires to keep track of the subscriptions and we need to unsubscribe OnDestroy() of the component. Moreover, we still would have a memory leak, if we had not make sure that we unsubscribe the inner subscription, before we re-assign a new subscription every 10 sec.
You may notice, that it is quite easy to forget a subscription. Therefore, we promote a solution with async pipes. As above, async pipes take care of the subscriptions via the HTML template: once we route away from the view template, all related async pipe subscriptions are cleaned automatically.
Step 8.3: replacing Subscriptions by async Pipes leading to a blinking Page
This section is kept for reference (or for tutorial purposes) only. It shows a simple solution, but it will lead to a blining page. You may want to skip this section and head over to one of the working solutions on the next step or the step thereafter.
Let us try to periodically update the data as follows:
- we add an interval function, which in turn is returning the getRestItems$() function every x seconds
- The subscription of the function is performed with a double async pipe
A first attempt of solving the task to display the most recent data is to periodically
src/app/app.component.ts
import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { AppService } from './app.service'; import { SafePost } from './safe-post.interface'; import { interval } from 'rxjs'; import { map } from 'rxjs/operators'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { posts$$: Observable<Observable<SafePost[]>>; constructor(private appService: AppService) {} ngOnInit() { this.posts$$ = this.getRestItemsIntervalBlinkingBehavior$$(); } getRestItemsIntervalBlinkingBehavior$$(): Observable<Observable<SafePost[]>> { return interval(10000) .pipe( map( counter => { console.log(counter + ': read restItems'); return this.getRestItems$(); } ) ); } // Read all REST Items getRestItems$(): Observable<SafePost[]> { return this.appService.getAll() .pipe(posts => posts); } }
In the template, we subscribe to the Observable of an Observable named „posts$$“. For that, we will need a duplicate async pipe:
src/app/app.component.ts
<h1>Blog Posts</h1> <ul> <li *ngFor="let post of (posts$$ | async | async)" [innerHTML]="post.title" (click)="selectedPost = post"> </li> </ul> <div *ngIf="selectedPost"> <h1 [innerHTML]="selectedPost.title"></h1> <div [innerHTML]="selectedPost.content"></div> </div>
This works in principle, but it has a very bad user experience: The browser toggles back and forth between a blank page and the desired list of posts:
^
| toggling every few seconds
v
🙁
Step 8.4: Interval Solution with Async Pipes and class Variable Assignment (working fine)
As before, this is a solution with all subscriptions on the component replaced by async pipes in the HTML template. However, this time, we will avoid the annoying blinking behavior by assigning the posts data to a class variable.
src/app/app.component.ts
import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { AppService } from './app.service'; import { SafePost } from './safe-post.interface'; import { interval } from 'rxjs'; import { map } from 'rxjs/operators'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { interval_MSEC = 10000; postsVoid$$: Observable<Observable<void>>; posts: SafePost[]; constructor(private appService: AppService) {} ngOnInit() { this.postsVoid$$ = this.getRestItemsIntervalVoid$$(); } getRestItemsIntervalVoid$$(): Observable<Observable<void>> { return interval(this.interval_MSEC) .pipe( map( counter => { console.log(counter + ': read restItems'); return this.assignRestItemsToPosts$(); } ) ); } assignRestItemsToPosts$(): Observable<void> { return this.appService.getAll().pipe( map(posts => { this.posts = posts; })); } }
Here, we have re-introduced our posts variable we have seen before in our variant using subscriptions in the component. However, this time, subscriptions are not needed within the component. Instead, the subscriptions are handled by the async pipes in the HTML template. This is the same as in the blinking section before, but the big difference is, that we display the list of posts that are controlled by the component instead of re-calculating the list from the postsVoid$$ variable. After resetting the postsVoid$$ variable every few seconds, the content of the posts variable is unchanged, until the answer of the REST service is retrieved and rendered. The blinking behavior is gone.
The HTML Template just will subscribe to the postsVoid$$ variable with a double async in a hidden element:
<h1>Blog Posts</h1> <div hidden>{{postsVoid$$ | async | async}}</div> <ul> <li *ngFor="let post of posts" [innerHTML]="post.title" (click)="selectedPost = post"> </li> </ul> <div *ngIf="selectedPost"> <h1 [innerHTML]="selectedPost.title"></h1> <div [innerHTML]="selectedPost.content"></div> </div>
In order to provide a „Loading..“ information to the user without the need to subscribe to the variable twice, we use the neat ngIf else functionality:
<h1>Blog Posts</h1> <ng-template #loading> Loading... </ng-template> <div *ngIf="!(posts == (postsVoid$$ | async | async)); else loading"></div> <ul> <li *ngFor="let post of posts" [innerHTML]="post.title" (click)="selectedPost = post"> </li> </ul> <div *ngIf="selectedPost"> <h1 [innerHTML]="selectedPost.title"></h1> <div [innerHTML]="selectedPost.content"></div> </div>
Now, the page reloads once and is shown stably to the user:
Still, it is updating automatically without page reload, if I change the title of one of the titles (Adding Swap to CentOS to Adding Swap to CentOS — Test in this case)
Step 8.5: Test of a cool intervalBackoff Function – With Subscription
What is exponential backoff?
There is a great resource Power of RxJS when using exponential backoff we will follow closely in this step. Exponential backoff means that we will start an interval at, say 1 sec and we will retry at increasing intervals thereafter:
Example: exp factor = 2 initial = 1 sec max = 60 sec n -> t ----------- 0 -> 1 sec 1 -> 2 sec 2 -> 4 sec 3 -> 8 sec 4 -> 16 sec 5 -> 32 sec 6 -> 60 sec 7 -> 60 sec 8 -> 60 sec ...
In this example, there is an initial refresh interval of 1 sec, and the subsequent refresh intervals calculate to n^2 sec. The upper limit is assumed to be set to 60 sec. However, this works also with any other factor > 1, e.g. exp = 1.4142:
e.g. exponential factor sqrt(2) = 1.4142... intervals: 1 sec - 1.4142 sec - 2 sec - 2.8284 sec - 4 sec ...
Why exponential Backoff?
The advantages of using an exponential backoff timer compared to a fixed interval are:
- each call of the backend costs resources on both sides: the client as well as the server. Exponential backoff is a good tradeoff between quick response and low resources.
- when using fixed intervals, the user may wait for the data forever. Consider an interval of 1 sec while the backend server is slow and will answer after 4 sec (this is close to the truth for the WordPress API). The client is sending a GET request every second, but it will cancel each request after every 1 sec; before having received the response. This will lead to the user waiting for the data forever. The usage of an exponential backoff will remedy the situation, since, finally, the interval will reach a value that is high enough for the server to respond. Still, we recommend using an initial timer that is high enough, so the first response will be displayed to the user.
Step 8.5.1: GIT checkout
We will start from the position before creating the interval code and create a new branch from there:
git checkout feature/0008-add-wordpress-interceptor git checkout -b feature/0009-refresh-via-intervalBackoff
Now let us add the code from Power of RxJS when using exponential backoff :
Step 8.5.1: Installation of backoff-rxjs
However, first, we need to add the backoff-rxjs package and save it in the package.json:
npm install backoff-rxjs --save
Note: the exponential backoff demo found on StackBlitz is extremely helpful!
Step 8.5.2: Adapt the Component
Now we can add the functionality to the component:
src/app/app.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Observable, pipe, Subscription } from 'rxjs'; import { AppService } from './app.service'; import { SafePost } from './safe-post.interface'; // exponentialBackoff: import { fromEvent } from 'rxjs'; import { sampleTime, startWith, switchMapTo, tap, map } from 'rxjs/operators'; import { intervalBackoff } from 'backoff-rxjs'; export const INITIAL_INTERVAL_MS = 5000; // 5 sec, choose larger than mean response time of REST service called export const MAX_INTERVAL_MS = 60000; // 1 min @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, OnDestroy { //posts$: Observable<SafePost[]>;posts: SafePost[]; mySubscription: Subscription; exponentialBackoffTimer: Observable; constructor(private appService: AppService) {} ngOnInit() { //this.posts$ = this.getRestItems$();this.exponentialBackoffTimer = fromEvent(document, 'mousemove').pipe( // There could be many mousemoves, we'd want to sample only // with certain frequency sampleTime(INITIAL_INTERVAL_MS), // Start immediately startWith(null), // Resetting exponential interval operator switchMapTo(intervalBackoff({ backoffDelay: (iteration, initialInterval) => Math.pow(1.2, iteration) * initialInterval, initialInterval: INITIAL_INTERVAL_MS, maxInterval: MAX_INTERVAL_MS })), // attaching the function that is to be reset: tap( n => { console.log('iteration since reset: ' + n); this.mySubscription = this.switchSubscription$( this.mySubscription, this.getAndAssignPosts$() ); }), ); } /* reSubscribeToGetAndAssignPosts$ Description: removes old subscription and returns new subscription Parameters: - mySybsription Returns: Subscription */ switchSubscription$(mySubscription: Subscription, myObservable$: Observable): Subscription { // remove old subscription: if (mySubscription !== undefined) { console.log('unsubscribing mySubscription'); mySubscription.unsubscribe(); } // assign new subscription: return myObservable$.subscribe(); } getAndAssignPosts$(): Observable<void> { return this.appService.getAll().pipe(map(posts => { this.posts = posts; })); } ngOnDestroy() { this.mySubscription.unsubscribe(); } }
Here, I have more or less copied the example, but I have added a call of the function switchSubscription$, which swaps the subscription from one REST response to the other. The REST response is produced by a function getAndAssignPosts$(), which calls the getAll() function of the REST service and assigns the posts array to our posts class variable.
In order to avoid memory leaks and concurrent subscriptions, we need to carefully keep track of the subscription and to unsubscribe, where necessary. We unsubscribe
- every time the subscription is renewed (within the switchSubscriptions$() function)
- when stopping the component routing away from the page (within the ngOnDestroy() function)
Step 8.5.3: Adapt the HTML Template
Since we have moved away from the original posts$ Observable, and are working with the simple posts variable directly, we also need to reflect this change in the HTML template:
src/app/app.component.html
<h1>Exponential Backoff Timer Test:</h1> <div>current number: {{exponentialBackoffTimer | async}}</div> <div>Move the mouse to reset it</div> <h1>Blog Posts</h1> <!-- <div *ngIf="null === (posts$ | async)">Loading...</div> --> <ul> <li *ngFor="let post of posts" [innerHTML]="post.title" (click)="selectedPost = post"> </li> </ul> <div *ngIf="selectedPost"> <h1 [innerHTML]="selectedPost.title"></h1> <div [innerHTML]="selectedPost.content"></div> </div>
Step 8.5.4: Exponential Backoff Timer Result
The result looks as follows:
The result is, as expected: the list of blog posts is updated every 5 sec, where the update timer increases by a factor of 1.2 every time.
Step 8.5.5: Exponential Backoff Timer Verification
But does the new page really reflect any changes, if the Backend REST service is changing the data? Let us test so now. For that, we update the title of the last blog post in the list:
Before:
Change the title of the „Adding Swap to CentOS“ in the WordPress Console:
After:
Without reloading the page, the change is reflected in the browser in a short time:
With that, we have proven that any change in the backend service will also be reflected in the browser after a short period of time.
How to retrieve the code
git clone https://github.com/oveits/consuming-a-restful-web-service-with-angular-vi git checkout feature/0009-refresh-via-intervalBackoff
Step 8.6: Test of a cool intervalBackoff Function – With pure Async Pipes
Step 8.6.1: GIT checkout
If not already done in a previous step, we will start from the position before creating the interval code and create a new branch from there:
git checkout feature/0008-add-wordpress-interceptor git checkout -b feature/0009-refresh-via-intervalBackoff
Step 8.6.2: Installation of backoff-rxjs
First, we need to add the backoff-rxjs package and save it in the package.json:
npm install backoff-rxjs --save
Note: the exponential backoff demo found on StackBlitz is extremely helpful!
Now let us add the code from Power of RxJS when using exponential backoff :
Step 8.6.3: Update the Component
We had used the Rxjs intervalBackoff timer by introducing subscriptions in the previous step 8.5. However, I have found a better way to manage the subscriptions: we will make use of async pipes similar to what we have done in the static interval case. Here is the code:
src/app/app.component.ts
import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { AppService } from './app.service'; import { SafePost } from './safe-post.interface'; // exponentialBackoff: import { fromEvent } from 'rxjs'; import { sampleTime, startWith, switchMapTo, map } from 'rxjs/operators'; import { intervalBackoff } from 'backoff-rxjs'; export const INITIAL_INTERVAL_MS = 5000; // 5 sec, choose larger than mean response time of REST service called export const MAX_INTERVAL_MS = 60000; // 1 min @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { posts: SafePost[]; n: number; exponentialBackoffTimer$$: Observable<Observable<void>>; constructor(private appService: AppService) {} ngOnInit() { this.exponentialBackoffTimer$$ = fromEvent(document, 'mousemove').pipe( // There could be many mousemoves, we'd want to sample only // with certain frequency sampleTime(INITIAL_INTERVAL_MS), // Start immediately startWith(null), // Resetting exponential interval operator switchMapTo(intervalBackoff({ backoffDelay: (iteration, initialInterval) => Math.pow(1.5, iteration) * initialInterval, initialInterval: INITIAL_INTERVAL_MS, maxInterval: MAX_INTERVAL_MS })), // mapping the code that returns an Observable of the void getAndAssignPosts$() function map( n => { console.log('iteration since reset: ' + n); this.n = n; return this.getAndAssignPosts$(); }), ); } getAndAssignPosts$(): Observable<void> { return this.appService.getAll().pipe(map(posts => { this.posts = posts; })); } }
You can see, that the code is simpler as before since we could get rid of the somewhat clumsy switchSubscription function. We just return the getAndAssignPosts$() function within a map (instead of a tap). With that, exponentialBackoffTimer becomes an Observable of an Observable of a void function. Therefore, we have denoted this with a double $ at the end of the variable name: exponentialBackoffTimer$$
Step 8.6.4: Update the HTML Template
Now, since the component did not subscribe to the Observable of the Observable, we need to do so in the HTML template:
<h1>Exponential Backoff Timer Test:</h1>
<div>current number: {{n}}</div>
<div>Move the mouse to reset it</div>
<h1>Blog Posts</h1>
<ng-template #loading>
Loading...
</ng-template>
<!-- if both, posts and (exponentialBackoffTimer$$ | async | async) are undefined, 'Loading...' is displayed: -->
<div *ngIf="posts != (exponentialBackoffTimer$$ | async | async); else loading"></div>
<ul>
<li *ngFor="let post of posts" [innerHTML]="post.title" (click)="selectedPost = post">
</li>
</ul>
<div *ngIf="selectedPost">
<h1 [innerHTML]="selectedPost.title"></h1>
<div [innerHTML]="selectedPost.content"></div>
</div>
Step 8.6.5: Test the intervalBackoff function (manually)
This is working like a charm:
Robustness:
I had the chance to test the behavior of the code in a Chrome browser on a bad Internet connection in a train, and it works as fine as it can work in such circumstances:
- in the beginning, the GET requests are canceled, since the response does not reach the browser in time. However, with the exponential backoff, the time between tries increases, and finally, the data is received and displayed.
- Once the data is displayed, it remains stable in the browser, even if subsequent REST requests remain unanswered.
Tuning
The first bullet point leads to a slightly longer load time when compared to a single REST request with no refresh, especially, if we work with an exponential factor close to one. We can improve this behavior by waiting long enough for the response of the initial REST, and if we waited substantially longer in case of a timeout. This can be done with two measures:
- measure the response time of the REST service under bad circumstances and choose an initial time that is higher than the mean response time
- Choose an exponential factor that is large enough. The default value of two seems to be a good compromise. In the tests, I have chosen 1.5, which seems also Okay. If you choose an exponential factor close to one (e.g. 1.1) and a too low initial time, it may take a long, long time, until the page is loaded successfully.
The End
All in all, the solution works fine, seems to be robust and there is no need to handle the subscriptions, as the async pipes in the template take care of it. Perfect.
Step 8.7: Getting rid of nested Subscriptions using RxJS flatMap
This subchapter has been moved to its own blog post found here. However, we can outline the results here:
- nested subscriptions are bad programming style
- chained async pipes we have used above are nested subscriptions, in fact
- flatMap can be used to get rid of nested subscriptions as well as for getting rid of chained async pipes
- switchMap works similar but does not work fine in our example, since it might cancel all HTTP GET requests before the response is received.
In the separate blog post, we have applied flatMap to our interval solution with async pipes. Here, let us apply the solution to the exponential backoff solution:
In this picture we see that the HTTP requests overlap each other in the beginning, but they are not cancelled, since we have used flatMap instead of switchMap.
The code can be seen here:
src/app/app.component.ts
import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { AppService } from './app.service'; import { SafePost } from './safe-post.interface'; // intervalBackoff: import { fromEvent } from 'rxjs'; import { sampleTime, startWith, switchMapTo, map, flatMap } from 'rxjs/operators'; import { intervalBackoff } from 'backoff-rxjs'; export const INITIAL_INTERVAL_MS = 1000; // 1 sec, choose larger than mean response time of REST service called export const MAX_INTERVAL_MS = 60000; // 1 min @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { posts: SafePost[]; n: number; exponentialBackoffTimer$: Observable; constructor(private appService: AppService) {} ngOnInit() { this.exponentialBackoffTimer$ = fromEvent(document, 'mousemove').pipe( // There could be many mousemoves, we'd want to sample only // with certain frequency sampleTime(INITIAL_INTERVAL_MS), // Start immediately startWith(null), // Resetting exponential interval operator switchMapTo(intervalBackoff({ backoffDelay: (iteration, initialInterval) => Math.pow(1.5, iteration) * initialInterval, initialInterval: INITIAL_INTERVAL_MS, maxInterval: MAX_INTERVAL_MS })), flatMap( n => { console.log('iteration since reset: ' + n); this.n = n; return this.getAndAssignPosts$(); }), ); } getAndAssignPosts$(): Observable { return this.appService.getAll().pipe(map(posts => { this.posts = posts; })); } }
We simply had to replace a map with a flatMap, with the effect that the type changes from a nested Observable<Observable<…>> to a simple Observable. Therefore, the chained async pipe | async | async
can be replaced by a single async pipe | async
in the HTML template:
<h1>Blog Posts</h1>
<ng-template #loading>
Loading...
</ng-template>
<!-- if both, posts and (exponentialBackoffTimer$$ | async | async) are undefined, 'Loading...' is displayed: -->
<div *ngIf="posts != (exponentialBackoffTimer$ | async ); else loading"></div>
<ul>
<li *ngFor="let post of posts" [innerHTML]="post.title" (click)="selectedPost = post">
</li>
</ul>
<div *ngIf="selectedPost">
<h1 [innerHTML]="selectedPost.title"></h1>
<div [innerHTML]="selectedPost.content"></div>
</div>
Note: the code of the working flatMap exponential backoff example can be downloaded with GIT like follows:
git clone https://github.com/oveits/consuming-a-restful-web-service-with-angular-vi git checkout feature/0014-refresh-via-intervalBackoff-using-flatmap
Summary
In this hello world style step-by-step guide, we have learned how to
- install an Angular 6 project using the Angular CLI in a Docker Container
- consume the WordPress API for retrieving and displaying a blog post title and HTML content.
- tell Angular to trust the „innerHTML“ content retrieved from the API
- refactor the service into its own file
- manage subscriptions through async pipes
References
- GIT Repo of this test: https://github.com/oveits/consuming-a-restful-web-service-with-angular-vi (git checkout 84bffd80 to reach the code at the end of this tutorial
- See here an earlier version of the Angular 4.3+ HttpClientModuel post.
- See this blog post for a more elaborate Angular 4 REST example promoting end-to-end tests.
- Consuming a REST API with Angular Http-Service in Typescript
- WordPress.com REST API console
- AngularJS REST hello world by Spring (Angular 1)
- An article showing how to separate the REST client into its own service: Getting Started With Angular 2 Step by Step: 6 – Consuming Real Data With Http
Appendix: Exploring the WordPress REST API
The WordPress API can be explored via the WordPress.com REST API console.
You need to login via and your WordPress.com credentials.
There seem to be several types and versions of APIs:
- WP.COM API
- v1.1 (default)
- v1
- WP REST API
- wp/v2
- wpcom/v2
In this Proof of Concept / Hello World, we are using the WP.COM API V1.1 to retrieve the content of a single WordPress blog post of mine:
https://public-api.wordpress.com/rest/v1.1/sites/vocon-it.com/posts/3078
However, we can also use the WP REST API wp/v2:
https://public-api.wordpress.com/wp/v2/sites/vocon-it.com/posts/3078
Because of the organization of the wp/v2 result, we need to exchange data.title
by data.title.rendered
and data.content
by data.content.rendered
in the HTML template, though.
Note: wp/v2 API does not seem to work with our new primary domain vocon-it.com. We have changed the primary stite from vocon-it.com to vocon-it.com a week ago.
Thx for the blog. I’m new in Angular and I’m reading a lot. Just a hint for your audience. It does not work fine when the data are to long or takes to much to load. So I change it to
@ViewChild(‚titleData‘) titleData: ElementRef;
@ViewChild(‚contentData‘) contentData: ElementRef;
constructor(private _http: Http) {
this.getMyBlog();
}
private getMyBlog() {
return this._http.get(‚https://public-api.wordpress.com/rest/v1.1/sites/vocon-it.com/posts/3078‘)
.map((res: Response) => res.json())
.subscribe(subscribedData => {
this.data = subscribedData;
this.loadTitle(subscribedData.title);
this.loadContent(subscribedData.content);
console.log(this.data);
});
}
private loadTitle(titleData) {
this.titleData.nativeElement.innerHTML = titleData;
}
private loadContent(contentData) {
this.contentData.nativeElement.innerHTML = contentData;
}
}
And that works fine.
Hi Mike,
thanks a lot for sharing. Which URL did you use to reproduce the problems with too long responses? Where did you find your solution?
Thanks,
Oliver
Thanks very much!
On https://yakovfain.com/2017/09/07/rxjs-essentials-part-5-the-flatmap-operator/, I have found a hint that it is bad practice to use nested subscriptions, as I did in in the last two chapters. There, I have combined interval refresh (with or without exponential back off) with HTTP requests in a nested manner. Yakovfain suggests to use mergemap/flatmap or switchmap instead. I will try that soon and I will keep you posted.
Your article helped me a lot, is there any more related content? Thanks!