Full Reactive Stack – The Angular frontend

Full Reactive Stack – The Angular frontend

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

> Full Reactive Stack with Spring Boot and Angular
GitHub
This guide focuses on the capabilities of Spring WebFlux and Spring Boot 2 to create a Reactive Web Application, supported by a code example that you can build step by step.
Part 3. Frontend with Angular, RxJS, SSE and EventSource (this article)
Table of Contents

This post is a blog version of one of the parts of the Full Reactive Stack with Spring Boot, WebFlux, Angular, RxJS and Eventsource Guide. The complete code sources are on GitHub.

Do you prefer an interactive way of learning or want a certificate of completion? Start this course now on Educative.io: Full Reactive Stack: Spring Boot 2 & Spring WebFlux

Get the complete guide in eBook formats now on Leanpub

Goal

This chapter focuses on the client’s 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? That will be our Angular application.

As a disclaimer, I’m far from being an expert in Angular and Typescript so the application has some room for improvement. I’ll skip the Angular basics and jump almost directly into the Reactive Service. If you want to learn Angular this guide is not the best choice; better check the official docs for example.

Why Angular?

To be honest, I chose Angular to implement the frontend side since I have some experience with this framework and it’s very backender-friendly. In any case, you could use the instructions in this guide to implement the solution in any other framework. That’s because the reactive functionality is supported by RxJS and the EventSource specification that is part of HTML5.

Creating the Angular application

To get the Angular project skeleton, the best option is to use the Angular CLI tool as described in the Setup guide on the Angular’s website.

You’ll also need Node.js (including npm) installed on your system.

Remember that the complete source code (Spring Boot, Angular, Docker) is available on GitHub: Full-Reactive Stack repository. If you find it useful, please star it!

UI Application Overview

What we’ll build is a simple application that allows us to load the available quotes from the server in a reactive manner (using Server-Sent Events) or in a classic blocking style. That option will come from the Angular Service we’ll use, so we’ll implement two different ones to showcase the differences. The UI contains a table and the goal is that, as soon as we receive book quotes, the table rows get updated.

To make it a 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 where the master rows are 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 uses the Quote Reactive Service and the Quote Blocking Service through Dependency Injection. The Quote Detail Component will be also included in the main component, and it just shows 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 familiar with Angular and want to learn more, I recommend you to follow the basic Tour of Heroes tutorial, in which you’ll grasp all these concepts.

In this chapter, we’ll focus on how the Quote Reactive Service works and its connection with the Component.

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. We’ll cover it in detail later.

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

import { Quote } from './quote';

import {Observable} from 'rxjs';

@Injectable()
export class QuoteReactiveService {

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

  getQuoteStream(page?: number, size?: number): Observable<Quote> {
    return new Observable<Quote>((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);
        observer.next(new Quote(json['id'], json['book'], json['content']));
      };
      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:

With EventSource we connect to the endpoint setting a Content-Type text/event-stream. This is implicitly set up by the API. That way, as we saw in the previous section, we open a channel in which the server will send us events. We create this connection implicitly when we construct the EventSource object, passing the endpoint in which the server exposes this functionality. Note that, depending on whether the page and size parameters are used in the method, we’re passing them as parameters in the URL.

Whenever we receive an event through the EventSource object (onmessage), we emit a new Quote object using Observable.next. Note that we don’t use EventSource (SSE) to pass data within our UI application. Instead, we use here a ReactiveX (RxJS) Observable object. A Subscriber of that Observable will receive events when the array gets updated – an action that we perform by calling next().

As detailed in the code comments, we’ll get an error when the stream is closed by the remote. We can identify that error if readyState is zero, and then we treat it as a normal situation: we complete the observer so the subscribers will know that they consumed all the available data.

The Angular Components – Quick Overview

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

This component uses the Reactive or the Blocking service depending on the user’s choice (via buttons on the HTML page as we’ll see in the next sub-section). It subscribes to the Observable<Quote> object and it pushes a new element in the data array every time a new item arrives. That’s the most important part of the component logic. To make sure that the UI refreshes after each update, we use Angular’s ChangeDetectorRef via injection to force change detection.

As you can see, no matter which service we’re calling, the result will be an Observable. The reason is that the blocking HTTP API we use also returns an Observable object. The only difference is that the blocking service returns a full array of Quotes, all at once.

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

import {Observable} from 'rxjs';
import {ChangeDetectorRef, Component} from "@angular/core";

@Component({
  selector: 'app-component-quotes',
  providers: [QuoteReactiveService],
  templateUrl: './quotes.component.html'
})
export class QuotesComponent {

  quoteArray: Quote[] = [];
  selectedQuote: Quote;
  mode: string;
  pagination: boolean;
  page: number;
  size: number;

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

  resetData() {
    this.quoteArray = [];
  }

  requestQuoteStream(): void {
    this.resetData();
    let quoteObservable: Observable<Quote>;
    if (this.pagination === true) {
      quoteObservable = this.quoteReactiveService.getQuoteStream(this.page, this.size);
    } else {
      quoteObservable = this.quoteReactiveService.getQuoteStream();
    }
    quoteObservable.subscribe(quote => {
      this.quoteArray.push(quote);
      this.cdr.detectChanges();
    });
  }

  requestQuoteBlocking(): void {
    this.resetData();
    if (this.pagination === true) {
      this.quoteBlockingService.getQuotes(this.page, this.size)
        .subscribe(q => this.quoteArray = q);
    } else {
      this.quoteBlockingService.getQuotes()
        .subscribe(q => this.quoteArray = q);
    }
  }

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

To complete the stack service-component-template, we have to provide HTML code that renders this component. This implementation is very simple since it just uses Angular basics with a for over an array. See the code below.

<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 quoteArray" [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>

Running the frontend

First, make sure the backend side is up and running, MongoDB included. If you cloned the git repository, you’ll need to download the node dependencies. From the angular-reactive folder, you have to execute:

$ npm install

Then, we can run the frontend application from the command line with:

$ npm run ng serve

If everything goes well, we’ll see the next screen when we navigate with our browser to http://localhost:4200.

Angular Reactive App

We have two buttons available to choose between a Reactive or a Blocking request. That’s 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 device, you may experience memory 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 chapter 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 on whether we execute a blocking request or a reactive one.

You should be able to feel how the user experience improves with the reactive approach. The Quotes are showing up as soon as the data is available, so we can access them almost immediately. We don’t need to wait for the server to return all elements (or a complete page). In the next chapter, we’ll go more into detail about performance and additional conclusions.

Angular Reactive App - Quotes

Did you like this post? Start the interactive course on Educative.io or get the complete Full Reactive series in a book format on Leanpub.

This article is part of a guide:
> Full Reactive Stack with Spring Boot and Angular
GitHub
This guide focuses on the capabilities of Spring WebFlux and Spring Boot 2 to create a Reactive Web Application, supported by a code example that you can build step by step.
Part 3. Frontend with Angular, RxJS, SSE and EventSource (this article)
Moisés Macero's Picture

About Moisés Macero

Software Developer, Architect, and Author.
Are you interested in my workshops?

Málaga, Spain https://thepracticaldeveloper.com

Comments