Building A Simple Reddit Client Web by Angular2 and RxJS — Part 1

Bootstrap App

You can find a number of Angular2 boilerplates out there. Below links are for reference.

Angular Official Website

Webpack Starter Boilerplate

angular2-seed

Source codes of working demo

Reddit API

Reddit serves both HTML and JSON out the of box.

Subreddit 9gag is used as an example in this post.

Below URL render HTML on your browser 
https://www.reddit.com/r/9gag

While below give you data in JSON format 
https://www.reddit.com/r/9gag.json

The JSON data will be fetched, parsed and rendered in the component we are going to create.

Creating RedditFeed Component

By using decorator @Component, we tell Angular Core our TypeScript class RedditFeed is an Angular Component, so that some logic such as change detection will be performed behind the scene.

app/components/reddit_feed.ts

import {Component} from 'angular2/core';  
import {Observable} from 'rxjs/Rx';
import {Http} from 'angular2/http';
@Component({
selector: 'reddit-feed',
templateUrl: 'app/components/reddit_feed.html',
styleUrls: ['app/components/reddit_feed.css']
})
export class RedditFeed {
...
}

Option selector define how this component will be declared in HTML. In this case it is declared in root index.html.

index.html

<body>  
<reddit-feed>Loading...</reddit-feed>
</body>

templateUrl defines where the View file html located. 
styleUrls accepts an array of css file paths with higher precedence.

We will come back later for the View.

Adding Angular2 Http Module

Angular2 comes with handy Http module which supports both Promise and Observable interfaces.

Injecting Http Providers

main.ts

import {HTTP_PROVIDERS} from 'angular2/http';  
...
bootstrap(RedditFeed, [...HTTP_PROVIDERS]);

When you inject the Http provider into your component (will see in next step), you have to inject the underlying dependent providers. HTTP_PROVIDERS is a shorthand of the list of injectable providers required by Http. So here you can inject them in root injector where you bootstrap the app.

Injecting Http into your component

app/components/reddit_feed.ts

export class RedditFeed {  
...
constructor(private _http: Http) {
}
...
}

That’s it. Parameters in constructor with qualifier private are immediate instance variables and assigned with injected value. Essentially it is a shorthand of:

export class RedditFeed {  
...
private private _http;
constructor(http: Http) {
this._http = http;
}
...
}

Fetching data from Reddit

app/components/reddit_feed.ts

export class RedditFeed {  
private _reddits$: Observable<Array<any>>;
private _redditDataUrl: string = 'http://www.reddit.com/r/9gag.json';
...
}

The new instance variable _reddit$ is an observable of array of reddits to be fetched. This article explains really well on the concept of reactive programming and hands-on RxJs coding. Some side notes:

  • ‘any’ tells Typescript to skip type checking.
  • ‘$’ suffix is a soft convention indicating the variable is a ‘stream’ of data.
private _initFeed() {  
this._reddits$ = this._http.get(this._redditDataUrl)
.map(response => response.json());
}

Next, we will init the stream by using Http get, which will return a stream of data. Worth noting at this point no actual XHR request is made through the network, it only creates a ‘blueprint’ of transformations. Here we first transform the XHR GET request into response, then transform the response into JSON data.

This ‘laziness’ nature of RxJS Observable make clean separation of observer logic from data subscription. And this is one distinguish feature from Promise. We will see more the power of this nature later.

Now, to trigger XHR call we will need someone to subscribe to the stream.

private _logFeed() {  
this._reddits$.subscribe(data => console.debug('data', data));
}

As an example above logger subscribes to stream and will print:

You can imagine we can create any number of observers to deal with the stream of reddit data, for instance save them to local storage.

Next, we need to call above functions at right moment.

ngOnInit() {  
this._initFeed();
this._logFeed();
}

ngOnInit is one of the useful lifecycle hooks of an Angular component.

From the official documentation:

ngOnInit is called right after the directive’s data-bound properties have been checked for the first time, and before any of its children have been checked. It is invoked only once when the directive is instantiated.

We can also put them in constructor but ngOnInit sounds to be the right place.

So far we successfully fetch data from Reddit and print in console. Let’s display the data better in HTML.

Creating View for our Component

Before we can display the data nicely, we need to further transform the raw data into more friendly format.

private _fetchReddits() {  
this._reddits$ = this._http.get(this._redditDataUrl)
.map(response => response.json())
.map(json => <Array<any>>json.data.children)
.map(children => children.map(d => {
return {
id: d.data.id,
title: d.data.title,
imageUrl: d.data.url
}
}));
}

The transformations are based on the data structure received from Reddit. 
Let’s verify by the logger:

Good to go to add our HTML.

app/components/reddit_feed.html

<div class='container'>  
<h3>Feeds from {{_redditDataUrl}}</h3>
<div class='reddit' *ngFor="#reddit of _reddits$ | async">
<h5 class='title'>{{reddit.title}}</h5>
<img src={{reddit.url}}/>
</div>
</div>

If you have coded with AngularJS above may look familiar to you.

Here *ngFor is Angular2 version of ng-repeat, which iterate over a collection variable render repeatedly the children elements. And the double curly braces interpolate the value. Better explain with result:

You may have noticed the ‘async’ right after the pipe(‘|’) character. Pipe is kind of Angular2 version of Filter in AngularJS, which transform data in the view file. Here async pipe interestingly handled the subscription of _reddit$and resolve the data as the result of pipe transform. Note that it works with Promise as well. It’s handy because you don’t have to create an intermediate variable in your component anymore as if you do in AngularJS controller.

You may have also noticed the broken image links, this can be easily fixed by filtering reddit without image extension in url.

private _fetchReddits() {
this._reddits$ = this._http.get(this._redditDataUrl)
.map(response => response.json())
.map(json => >json.data.children)
.map(children => children.filter(d => (
['png', 'jpg'].indexOf(d.data.url.split('.').pop()) != -1
)))
.map(children => children.map(d => {
return {
id: d.data.id,
title: d.data.title,
imageUrl: d.data.url
}
}));
}

Further Readings and References

Official API Documentation 
https://angular.io/docs/ts/latest/guide/ 
https://angular.io/docs/ts/latest/api/

Egghead Online Courses 
https://egghead.io/technologies/angular2

Ng-book2 
https://www.ng-book.com/2/

Source codes of working demo

Show your support

Clapping shows how much you appreciated Benny Ng’s story.