Building our new Dashboard using Angular 2

Gaurav Mukherjee
HyperTrack
Published in
3 min readMay 13, 2016

Angular 2 is here. The Angular team had stormed the Angular developer community by announcing that its new version will be a complete rewrite with no backward compatibility. AngularJs was originally built for designers for rapid prototyping, performance had never been its strength. Angular 2 was being written to build large enterprise-grade apps.

When Angular team released the first beta of Angular 2 in December 2015, we were in the process of building the HyperTrack Dashboard for our users to track pickups and deliveries in real-time. We found the following fundamental design decisions in Angular very interesting.

TYPESCRIPT

Angular 2 uses TypeScript (originally developed by Microsoft). Typescript is a typed superset of javascript that compiles to plain javascript. This allows us to catch a number of bugs at compile time. In addition, with Intellisense, writing and refactoring code is a breeze.

WEB WORKERS

In Angular 2, the framework is decoupled from the DOM via a higher level API. This makes it really easy to use web workers to get a performance boost and also to implement features like the trip replay feature on the dashboard.

MODULE LOADER

Angular 2 uses system.js which is a universal module loader. This allows you to structure your code properly and makes maintaining large code bases easier.

RX.JS

Angular 2 uses Rx.js to make http calls by default. Rx.js uses Observables instead of promises. Unlike promises which get executed right when created, observables get executed only when they are subscribed to. The following examples illustrate the advantages of using this pattern.

Concatenate paginated data: We display a list of all live trips. We used only endpoint which are available to our customers, which provides a paginated array of live trips.

{ 
next: “next_url”,
previous: ‘previous_url’
results: []
}

We could fetch data from next_url , wait for the response, concatenate the data to results array and again fetch next page data, and do this in a loop. Instead, we used the concat operator of Rx.js to get a single observable which emits the concatenated data for us.

import {Injectable} from “angular2/core”;
import {Http} from “angular2/http”;
import {Observable} from “rxjs/Rx”;
import {UrlService} from “../value/url.service”;
@Injectable()
export class PageService {
public query
constructor(private _http: Http, private _urlService: UrlService){

}
all(url) {
return this._http.get(url)
.map(res => res.json()
.concatMap(data => {
if (data.next) {
return this.all(data.next)
.map(resultsToJoin => {
return […data.results, …resultsToJoin
});
} else {
return Observable.of(data.results);
}
});
}
}

this.pageService.all(url) would emit array of all live trips.

Poll on an endpoint: If we want to poll an endpoint to update the data on the client side, we can use interval operator of Rx.js.

import {Injectable} from “angular2/core”;
import {Http} from “angular2/http”;
import {Observable} from “rxjs/Rx”;
import {UrlService} from “../value/url.service”;
@Injectable()
export class DriverService {
constructor(private url: UrlService, private http: Http) {
}
liveTrips(driverId) {
return this.http.get(this.url.baseUrl + “trips/expanded/?is_live=True&page_size=” + 1 + “&ordering=-started_at&driver_id=” + driverId)
.map(res => res.json())
}
pollLiveTrips(driverId, time = 60000) {
return Observable.interval(time)
.flatMap(() => {
return this.liveTrips(driverId);
})
}
}

this.driverService.pollLiveTrips(driverId) emits the latest data after every fixed interval. When the component which uses this polling endpoint gets destroyed, using ngOnDestroy lifecycle hook, we should unsubscribe to the observable

Fetch additional data from a different endpoint: Suppose we have a trip entity which contains driver id. We can fetch driver data using RESTFull endpoint, e.g./drivers/2/.

//example response
{
id: “2”,
driver_id: “123”
}

Expected response is

//expected response from observable
{
id: “2”,
driver_id: “123”,
detailed_driver: {
name: “abc”,
id: “123”
}
}

The following Rx.js operator uses these two endpoints to get final expected response from a single observable using flatMap operator andObservable.forkJoin.

this.http.get(‘/trips/someid’)
.map(res => res.json())
.flatMap(res => {
return Observable.forkJoin([
Observable.of(res),
this.http.get(‘/drivers/’+res[‘driver_id’])
.map(res => res.json())
]);
})
.map(res => {
res[0][‘detailed_driver’] = res[1];
return res[0]
})
.subscribe( (data) => { }
);

We can use these operators to build some useful utilities, which otherwise takes lot more code to accomplish the same result.

FINAL THOUGHTS

Angular 2 made our web development a wonderful experience. When we started, it was still in beta, but our bet on Angular 2 has paid off. As the framework matures further, we are looking forward to taking advantages of other upcoming features like offline compilation, server-side rendering, progressive apps and more, to provide a modern and performant web experience on the dashboard.

To start using our new dashboard, request access to our API here.

--

--