Menu Home

Building a train departure board using Angular and Huxley

This article provides a tutorial on how to build a train station departure board which displays real time information using Angular and Huxley.

You can see a working example here. The project can be found on GitHub.

Prerequisites

To build the departure board I used:

  • Angular 7.2.15
  • VS Code
  • Access token (see below)
  • Huxley and a cloud provider to host it (see below)

The need for Huxley

The departure board obtains it’s data from Darwin which is the UK rail industry’s official train running information engine.

It is possible to interact directly with Darwin but as you may know working with a SOAP based API is no fun and crafting XML is not something that you can get enjoyment from. If only there was a service that sat in front of Darwin that would allow you to GET easy to parse JSON.

Enter Huxley.

Huxley abstracts away all the pain of using Darwin. Using intuitive URL query parameters it is effortless to obtain train information. The API Endpoints are described here.

Access Token

In order to use Darwin you will need an access token. You can obtain one by registering here.

Hosting your own instance of Huxley

Once you have an access token you can try out Huxley by submitting get requests here.

To build anything meaningful you will need to arrange hosting for your instance of Huxley. This isn’t as daunting as you may think and the Huxley wiki page on Git Hub has a straightforward quick start guide using either App Harbor or Azure.

Securing the Darwin access token

The example that is built here doesn’t have a back end and if the API key is kept within your Angular App it will be visible when viewing the networking tab within the browser developer tools.

I have added the token to my instance of Huxley running in Azure. Whilst this means the access key is not visible a further step will be required to ensure that your instance of Huxley where ever that is hosted should only accept requests from your own application.

Building the Angular app

Below is the code used to build the Angular app. Under each component you will find useful comments and explanations.

Create a new Angular App and add Bootstrap

Start by creating a brand new Angular app. You can call it whatever you like. Mine is called LiveDepartureBoard. Once the app has been created add Bootstrap 4.

In this earlier post I explain the steps required to add Bootstrap 4 to an Angular App.

Model

Within a folder called “shared” I created the class which is used as the blueprint for the train data I want to use.

departureBoard.model.ts

export class DepartureBoard {
    platform: string;
    trainTime: string;
    destination: string;
    etd: string;
    cancelReason: string;
    callingAt: string;


    constructor(platform: string, trainTime: string, destination: string, etd: string, cancelReason: string, callingAt: string) {
        this.platform = platform;
        this.trainTime = trainTime;
        this.destination = destination;
        this.etd = etd;
        this.cancelReason = cancelReason;
        this.callingAt = callingAt;
    }
}

Service

Also in the shared folder is the service which invokes Huxley to return departure information for Loughborough. Feel free to change this to a station of your choice. The codes can be found here.

As there are many things that could go wrong when calling an API the service also contains some rudimentary error handing.

traininformation.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })

export class TrainInformationService {

    constructor(private http: HttpClient) { }

    url = 'https://ichuxley.azurewebsites.net/departures/lbo';

    getTrainInformation(): Observable {
        return this.http.get(this.url,
            {
                params:
                {
                    expand: 'true'
                }
            }
        ).pipe(catchError(this.handleError));
    }

    private handleError(error: HttpErrorResponse) {
        if (error.error instanceof ErrorEvent) {
            // a client-side or network error has occurred
            console.log('An error has occurred:', error.error.message);
        } else {
            // The backend returned an unsuccessful response code
            // The response body may contain clues as to what went wrong
            console.log(`Backend returned code ${error.status},` +
                        `body was: ${error.error}`);
        }
        // return an observable with a user-facing error message
        return throwError('Something bad has happened; please try again later.');
    }
}

Component

The component subscribes the service and adds the results to an object constructed from the DepartureBoard class. All the information is then easily available for the UI to interact with.

The component also creates the clock that is seen on the bottom right of the departure board.

app.component.ts

import { Component, OnInit, OnDestroy, Input } from '@angular/core';
import { TrainInformationService } from './shared/traininformation.service';
import { DepartureBoard } from './shared/departureBoard.model';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit, OnDestroy {

  trainServices: any;
  callingAt = Array<string>();
  callingAtCSV: string;
  departureBoard = Array<DepartureBoard>();
  subscription: Subscription;
  noTrainInformation: boolean;
  time = new Date();
  timer: number;

  constructor(private trainInformationService: TrainInformationService) {}

  ngOnInit() {
    this.getDepartureBoard();
    this.getTime();
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
    clearInterval(this.timer);
  }

  getDepartureBoard() {
    this.subscription = this.trainInformationService.getTrainInformation().subscribe( data => {

      // useful for debugging - outputs the json returned by huxley
      // console.log(data);

      // If there is no train information no need to do any further processing other than set the flag
      // to tell the UI to display a suitable message
      if (!data.trainServices) {
        this.noTrainInformation = true;
        return;
      } else {
        this.noTrainInformation = false;
      }

      // only interested in a subset of the data returned.
      this.trainServices = data.trainServices;

      for (const trainService of this.trainServices) {
        for (const callingPoint of trainService.subsequentCallingPoints) {
           for (const station of callingPoint.callingPoint) {
             this.callingAt.push(station.locationName);
           }
        }
        this.callingAtCSV = this.callingAt.join(', ');

        this.departureBoard.push(new DepartureBoard(trainService.platform,
                                                    trainService.std,
                                                    trainService.destination[0].locationName,
                                                    trainService.etd,
                                                    trainService.cancelReason,
                                                    this.callingAtCSV));
        this.callingAt = [];
      }
    });
  }

  getTime() {
    // window.setInterval is used as a workaround to compiler warning: Timer is not assignable to type number
    // https://github.com/TypeStrong/atom-typescript/issues/1053
    this.timer = window.setInterval(() => {
      this.time = new Date();
    }, 1000);
  }
}

UI

Using bootstrap 4 to style the UI takes the form of a typical station departure board.

app.component.html

<div class="container border" *ngIf="noTrainInformation">
      <div class="row">
         <div class="col text-center">There is currently no train information available</div>   
      </div>
</div>
<div class="container header">
   <div class="row">
      <div class="col-12"><h1>Departures</h1></div>
   </div>
   <div class="row">
      <div class="col-sm-1">Time</div>
      <div class="col-sm-8">Destination</div>
      <div class="col-sm-1 text-md-center">Platform</div>
      <div class="col-sm-2 text-md-center">Expected</div>
   </div>
</div>
<div class="container border" *ngFor="let train of departureBoard">   
   <div class="row">
      <div class="col-sm-1">{{ train.trainTime }}</div>      
      <div class="col-sm-8 TrainDestination">{{ train.destination }}</div>
      <div *ngIf="train.platform; then thenPlatformBlock else elseNoPlatformBlock"></div>
      <ng-template #thenPlatformBlock><div class="col-sm-1 text-md-center">{{ train.platform }}</div></ng-template>
      <ng-template #elseNoPlatformBlock><div class="col-sm-1 text-md-center">To be confirmed</div></ng-template>
      <div class="col-sm-2 text-md-center">{{ train.etd }}</div>
   </div>
   <div class="row">
      <div class="col" *ngIf="train.cancelReason; thenCancelBlock"></div>
      <ng-template #thenCancelBlock>{{ train.cancelReason }}</ng-template>
   </div>     
   <div class="row no-gutters">            
      <div class="col-sm-12 col-md-1">Callling at:</div>         
      <div class="col-sm-8 col-md-8">{{ train.callingAt }}</div>
   </div>
</div>
<div class="container footer">
   <div class="row">
      <div class="col text-right">Time now: {{ time | date: 'HH:mm:ss' }} </div>
   </div>
</div>

app.component.css

.container {
    background: black;
    color: white;    
    font-family: 'Arial', cursive;        
    font-size: large;
}

.TrainDestination {
    color: rgb(255, 174, 0)
}

.header, .footer {
    background: hsla(226, 98%, 49%, 0.829)
}

At this point you will have a departure board displaying real time information.

On to version 2

I have enjoyed working on this project which has taught me a lot about Angular, Azure and Huxley.

I am now looking at further features I can introduce for version 2 such as the ability to view a station of the users choosing. This brings along some interesting design decisions on what type of controls would be most suitable; a drop down list, a text box with auto complete.

I am also keen to start refactoring the app.component.ts. This was fine for version 1 and getting something off the ground. Now is the time to see where I can make that component leaner.

Acknowledgements

James Singleton for his superb work on Huxley

BFG Repo-Cleaner for tidying up my Git History.

This medium post for helping me understand how to deploy to Firebase

Categories: Angular

oraclefrontovik

Developer

1 reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.