Full Reactive Stack – The Angular frontend

Full Reactive Stack – The Angular frontend

In the previous post, we created a Spring Boot application which offers a Reactive Web API. We’ll cover in this one how to implement the client-side using Angular with EventSource and RxJS’ Observable.

Guide’s Table of Contents

  1. Full Reactive Stack - Introduction
  2. Backend side with Spring Boot, WebFlux and MongoDB
  3. Frontend Reactive Client with Angular and EventSource (this post)
  4. Running the application, Comparing WebFlux and MVC, and Conclusions
Do you prefer the print-friendly version of the guide? Get access now to the mini-book (37 pages) at a reduced price. Download your copy on LeanPub.

Goal

This part of the guide is focused on the client side of the system. We already created a Reactive Web Endpoint that can supply Server-Sent Events but, how to use it in a more realistic example? I’ll try to cover the answer to that question.

As a disclaimer, I’m far from being an expert in Angular and Typescript so the application has room for improvement. I’ll skip the standard basics of Angular that the client contains and jump almost directly into the Reactive Service. If you want to learn Angular don’t use this post, check a proper tutorial (those are referenced below).

Why Angular?

To be honest, I chose Angular (version 4) to implement the frontend side since I have some experience with this framework. In any case, you could use the instructions given here to implement the solution in any other framework since the reactive functionality is mainly supported by RxJS and EventSource (a specification that is part of HTML5).

You may argue that other web frameworks have better support for reactive patterns. But, in any case, bear in mind that the reactive approach in this example focuses on the interface with the server. The rest of the client code is quite simple and doesn’t use publisher/subscriber pattern between web components.

Creating the Angular application

To get the Angular project skeleton you have two alternatives:

For both alternatives, you need also Node.js (including npm) installed on your system. The full version of this application has been built using the Angular CLI, so I recommend you to download it as indicated in the linked documentation.

All the source code (spring-boot, Angular, Docker) is available on GitHub: Full-Reactive Stack repository. If you find it useful, please give it a star!

Application Overview

What we’ll build is a simple application that allows us to ask for the available quotes to the server in a reactive manner (using Server-Sent Events) or in a classic blocking style. That distinction will come from the Angular Service we’ll use, so we’ll implement two to showcase the differences. As soon as it receives quotes, the list will get updated.

To make it a little bit more interesting, if we click on a quote the full content will be displayed in a different component on the screen. So, it’s basically a master-detail view with the master rows being loaded dynamically using Reactive patterns.

Angular Reactive diagram

As you can see in the figure above, our application has three simple Angular components and two services. The component at the top of the hierarchy is the App Component, which acts just as a wrapper for the Quotes Component in our scenario.

The Quotes Component is the core component of our application and it utilizes the Quote Reactive Service and the Quote Blocking Service through Dependency Injection. The Quote Detail Component will be placed on the right side, and it’s just a simple one showing the full contents of the selected Quote object.

The rest of the files in the GitHub repository are tests, styles, dependency management, module definitions, etc. As mentioned before, if you’re not very familiar with Angular JS I recommend you to follow the basic Tour of Heroes tutorial, in which you’ll grasp all these concepts. For this post, let’s focus on how the Quote Reactive Service works and its connection with the Component.

Get the book Practical Software Architecture

The Angular Reactive service

Let’s have a look at the code of the reactive service that interacts with the backend using Server-Sent Events.


import { Injectable } from '@angular/core';

import { Quote } from './quote';

import * as EventSource from 'eventsource';
import {Observable} from 'rxjs/Observable';

@Injectable()
export class QuoteReactiveService {

  quotes: Quote[] = [];
  url: string = 'http://localhost:8080/quotes-reactive';
  urlPaged: string = 'http://localhost:8080/quotes-reactive-paged';

  getQuoteStream(page?: number, size?: number): Observable<Array<Quote>> {
    this.quotes = [];
    return Observable.create((observer) => {
      let url = this.url;
      if (page != null) {
        url = this.urlPaged + '?page=' + page + '&size=' + size;
      }
      let eventSource = new EventSource(url);
      eventSource.onmessage = (event) => {
        console.debug('Received event: ', event);
        let json = JSON.parse(event.data);
        this.quotes.push(new Quote(json['id'], json['book'], json['content']));
        observer.next(this.quotes);
      };
      eventSource.onerror = (error) => {
        // readyState === 0 (closed) means the remote source closed the connection,
        // so we can safely treat it as a normal situation. Another way of detecting the end of the stream
        // is to insert a special element in the stream of events, which the client can identify as the last one.
        if(eventSource.readyState === 0) {
          console.log('The stream has been closed by the server.');
          eventSource.close();
          observer.complete();
        } else {
          observer.error('EventSource error: ' + error);
        }
      }
    });
  }

}

Now, some explanation about the most important ideas in that code:

  1. With EventSource we connect to the endpoint setting a Content Type text/event-stream. That way, as we saw in the previous section, we open a channel in which the server will send us events, using the so-called Server-Sent Events mechanism. We connect to the backend from our code by creating the EventSource object, passing the endpoint in which the server exposes this functionality. The code includes a condition to pass the page and size parameters only if you use them in the URL.
  2. Whenever we receive an event through the EventSource object (onmessage), we update the Quote array including the new element. However, since EventSource is a very specific object for connecting and receiving data from the server, we don't use it directly in other parts of our code: we create and handle a ReactiveX (RxJS) Observable object. We apply here this reactive pattern over the Quote array, meaning that any Subscriber to that Observable can receive events when the array gets updated - an action that we perform by calling next(this.quotes).
  3. As detailed in the code comments, whenever the stream is closed by the remote, we treat it as normal and we complete the observer.

The Angular Components - Quick Overview

The part interacting with the Services (both blocking and reactive) is the Quotes Component: quotes.component.ts


import { Component } from '@angular/core';

import { Quote } from './quote';
import { QuoteReactiveService } from './quote-reactive.service';
import { QuoteBlockingService } from './quote-blocking.service';

import { Observable } from 'rxjs/Observable';

@Component({
  selector: 'app-component-quotes',
  providers: [QuoteReactiveService],
  templateUrl: './quotes.component.html'
})
export class QuotesComponent {
  quotes: Observable<Quote[]>;
  selectedQuote: Quote;
  mode: String;
  pagination: boolean;
  page: number;
  size: number;

  constructor(private quoteReactiveService: QuoteReactiveService, private quoteBlockingService: QuoteBlockingService) {
    this.mode = "reactive";
    this.pagination = true;
    this.page = 0;
    this.size = 50;
  }

  requestQuoteStream(): void {
    if (this.pagination === true) {
      this.quotes = this.quoteReactiveService.getQuoteStream(this.page, this.size);
    } else {
      this.quotes = this.quoteReactiveService.getQuoteStream();
    }
  }

  requestQuoteBlocking(): void {
    if (this.pagination === true) {
      this.quotes = this.quoteBlockingService.getQuotes(this.page, this.size);
    } else {
      this.quotes = this.quoteBlockingService.getQuotes();
    }
  }

  onSelect(quote: Quote): void {
    this.selectedQuote = quote;
  }
}

This component is using one of the services (via dependency injection) when the user presses a button, depending on the configuration that he chooses. It holds the result inside an Observable<Quote[]> object. That’s the most important part of it. Now the quotes field can be used with an async pipe from our HTML template (quotes.component.html) to display them on our page (see below).

As you can see, no matter to which service we’re calling, the result will be an Observable. The implementation is neutral so we can see the differences between the two approaches in a transparent manner.

In our HTML template, we’ll pass the Observable object so that means there is an implicit subscription mechanism (from the template to the component) that updates our page whenever an event is received from the server. To get that working in Angular we use the async pipe in our iteration loop:

quotes.component.html

<div class="row">
  <div class="col-md-8">
    <p>
      <label>Pagination:
        <input type="checkbox" [checked]="pagination" (change)="pagination = !pagination" >
        <label>Page #: 
          <input type="text" [(ngModel)]="page" size="2">
        </label>
        <label>Page Size:
          <input type="text" [(ngModel)]="size" size="2">
        </label>
      </label>
      <button (click)="requestQuoteStream()">Reactive Request</button>
      <button (click)="requestQuoteBlocking()">Blocking Request</button>
    </p>
    <table class="table">
      <thead>
      <tr>
        <th>Quote ID</th>
        <th>Book</th>
        <th>Quote content</th>
      </tr>
      </thead>
      <tbody>
      <tr *ngFor="let quote of quotes | async" [class.success]="quote === selectedQuote" (click)="onSelect(quote)">
        <td>{{quote.id}}</td>
        <td>{{quote.book}}</td>
        <td>{{quote.content.substr(0, 30)}}...</td>
      <tr>
      </tbody>
    </table>
  </div>
  <div class="col-md-4">
    <div class="message">Click on a quote for more details</div>
    <app-quote-detail [quote]="selectedQuote"></app-quote-detail>
  </div>
</div>

As per the documentation, the Async pipe subscribes to the Observable and marks the component as changed whenever a new event comes. That’s exactly what we want to do there, hence the *ngFor using the quotes array has the async pipe.

Running the frontend

No need to say that you should make sure that the backend side is up and running.

We can run our application from the command line (inside the Angular folder) by running npm run ng serve. Then, if we access to http://localhost:4200 with our browser, we’ll see this screen:

Angular Reactive

We have two buttons available to choose between a Reactive or a Blocking request, being that the switch between calling one service or the other. Besides, we can also pick if we want pagination and its corresponding configuration.

Keep in mind that, depending on your machine, you may have problems when requesting all the data at once (no pagination). In that case, you can change the backend code (controller) to limit the number of quotes returned. Check the backend post for more details.

We can now play with all these options and see how the quotes load either at once or one by one depending if we execute a blocking request or a reactive one. Can you spot the differences? Which one gives a better user experience in your opinion? We’ll cover that in the next section/post of this guide.

Angular Reactive Quotes

Continue reading the Guide

Continue reading the post about comparing WebFlux and Blocking web calls, which also details how to run the whole application with Docker.

Do you prefer the print-friendly version of the guide? Get access now to the mini-book (37 pages) at a reduced price. Download your copy on LeanPub.

Continue reading:

Moisés Macero's Picture

About Moisés Macero

Software Developer, Architect, and Author.
Do you need help?

Amsterdam, The Netherlands https://thepracticaldeveloper.com