If you have ever built an Angular 2 application before, you’ll know that setting up and bootstrapping an application can take a significant amount of time. Thankfully, the Angular team have rolled out Angular CLI, a command line interface that makes creating and scaffolding an application significantly easier.
In this post, we’ll build an entire Hacker News client using Angular CLI, RxJS Observables and Webpack as our module loader.
We’ll go through building the entire application step by step. Throughout this post, I’ll try my best to explain my thought process as well as some of the mistakes I’ve made and what I did to fix them.
Here’s a rundown of what we’ll be doing.
- We’ll start by building our basic setup first, the front page of Hacker News
- We’ll then wrap an Observable Data Service to load data asynchronously
- To allow the user to navigate between different pages and story types, we’ll add routing using the Angular Component Router
- Finally, we’ll add routes to allow the user to navigate to item comments and user profiles.
This visual tutorial should make you feel a little more comfortable building an Angular 2 application from small modular parts as well as creating an app from scratch all the way to completion. We’ll also briefly go over some important topics and understand how they apply to an actual application, which includes:
- View Encapsulation
Once you have the required Node and NPM versions, you can install the CLI through your terminal.
You can then create and start your application.
If you now open
https://localhost:4200/, you’ll see the application running.
Pretty cool huh? Angular CLI used to use SystemJS as the module bundler and loader. Using SystemJS had a few quirks including long loading times and a lengthy process just to add third party libraries. So to make things simpler and faster, the Angular CLI team have moved from SystemJS to Webpack!
Bootstrapping an application with CLI uses Angular’s latest release version, but let’s go over one of the biggest changes that happened with the release of RC5, the
@NgModule decorator. We can see it being used in the
So what exactly is happening here? The
@NgModule decorator specifies all declarations (components, directives and pipes), library imports (such as
HttpModule) and providers (a single-instance service for example) that we’ll be using in our application.
You can probably already see how much more organized it is to not need to specify all our module-level components, directives, pipes and so forth in each of our components.
Let’s get ready to rumble
Let’s set up Sass as our CSS preprocessor. The CLI makes it simple for a project that’s already been started.
Note: If you’re seeing a weird error here, you can just set the default style extension to
angular-cli.json instead and restart the server. Take a look at this issue.
Now that we have everything set up, we can create our first few components. To start things off, we’ll create a
You’ll notice that a
header folder is immediately created and scaffolded with the following files created.
I’m only joking, unit testing is always important for apps that go to production. We won’t be doing them for this tutorial however so feel free to delete/comment out the
Take a look at
app.module.ts once again and you’ll notice that our component is now declared there as well.
If you take a look at
header.component.ts, you can see that its component selector is
app-header. Let’s add this in our root component,
Running the application will show you that the header component loads successfully.
Sweet, now let’s add some markup and styling.
The styling in
app.component.scss can be found here. Now let’s work on the header.
And similarly, you can find the styling for this component here. Running the application gives us the following result.
Since we want this application to be as responsive as possible, it’s important to check how it looks with different screen sizes regularly. Let’s adjust our viewport to see how it would look on a mobile device.
As you can see, there seems to be an offset from the edge of the page. This is because the
body element has a bult-in offset (through
margin) that shows in almost all modern browsers.
But if you take a look at
app.component.scss, we explicity set
margin: 0 for screen sizes less then 768px.
So why isn’t this rendering the way it should? This is because of the way Angular encapsulates CSS styles onto a component. I won’t be going into too much detail here, but there are three different ways Angular does this.
None: Angular doesn’t do anything. No encapsulation and no Shadow DOM, this is just like adding styles regularly. Adding a style will apply to the entire document.
Emulated: Angular emulates Shadow DOM behaviour. This is the default.
Native: Angular uses the browser’s native Shadow DOM completely (make sure you’re using a browser that supports this.
In our root component, we’re trying to add styles to the
body element which really doesn’t make sense if you think about it. Our root component is within
body, not the other way around, hence why it’s styles will not be affected. We can work around this by telling Angular to not do any view encapsulation in this component whatsoever.
Take a look at our application once more and you’ll notice that the styles have now been applied to
body. This is because all of the styles in this component now affect the entire document.
But wait a minute, was all of this really necessary? I see a
styles.css file in our
src folder. Isn’t this for global styles? Can’t we just add a class here to style
Yes you can, but hey at least we learned something here.
Let’s create two more components,
Footer. Stories represent posts in Hacker News, and we’ll start out with a skeleton just to get an ordered list in place.
We’ll also need to update our root component to show these components.
Let’s see what our page is looking like.
Since each post, or item, will have its own attributes, it makes sense to create a component for this as well.
Once we start getting real data, we’ll need to pass down the item identifier from the story component to it’s child item component. In the meantime, let’s just pass down the list index as
Refreshing the application will give you the same result, showing that the index parameter is successfully being passed down using the
We have a basic skeleton of the home page done and that’s a good start. Here’s the link for the source code for this step.
RxJS and Observables
Before we start fetching real data, let’s briefly go over the concept of RxJS and observables.
Angular’s HTTP client allows you to communicate with a server, and you need it to fetch data from anywhere. To fetch data from a server, the first thing you would most likely do is pass the resource URL through an
http.get call. But what gets returned exactly?
In Angular 2, we use the RxJS library to return an
Observable of data, or an asynchronous stream of data. You may already be familiar with the concept of Promises and how you can use them to retrieve data asynchronously. Observables obtain data just like promises do, but they allow us to subscribe to the stream of data and respond to specific data changes that it emits.
The diagram above depicts the events that occur when a user clicks on a button. Notice how this stream emits values (which represent the click events), an error as well as a completed event.
The entire concept of using Observables in your application is known as Reactive Programming.
Observable Data Service
Okay, now it’s time to start retrieving some real data. To do this, we’re going to be creating an Observable Data Service and injecting it into our components.
This creates and sets up a service file for us. Now before we dive in, let’s try to understand how the official Hacker News API works. If you take a look at the documentation, you’ll notice that everything (polls, comments, stories, jobs) is just an item distinguishable though an
id parameter. This means any item’s information can be obtained from their specific URL.
Now if we want to obtain information like front page ranking, we’ll need to use another endpoint specific to the type of stories. For example, top stories can be retrieved like this.
So to show the top stories on the front page, we’ll need to first subscribe to this endpoint and then subsequently subscribe to each item. Let’s dive in.
Since we would want a single instance of the service throughout the entire app, let’s include it in the
provider metadata of
Now let’s add the request method to our service.
As we mentioned previously, the
http.get call returns an Observable of data. If you look at
fetchStories, we take in the Observable and then
map it to a JSON format. Let’s see how we handle this Observable in our component.
ngOnInit hook, which fires when the component is initialized, we
subscribe to the data stream and set the
items attribute to what gets returned. In our view, the only thing we’re going to add is a
SlicePipe to show 30 list items instead of all 500 which gets returned.
Now if you run the application, you’ll see a list of item ids populated.
Since we have the item id’s being passed down successfully to each of the
item components, let’s set up another Observable subscription for each item to show their details. To do this, let’s start by adding a new method to our service.
And now we need to modify our
item component a bit.
Nice and straightforward. For each item, we’re subscribing to their respective stream. In the markup, we can see that when the response hasn’t been received yet, we have a loading section where we can show a loading indicator of some sort. Once the item loads from the Observable, it’s details will show. Click here to see all the files for this component including styling.
Note: You may be wondering where the
amTimeAgo pipes came from. The time parameter for each item is in Unix format. To convert this into something we can understand, I use moment.js pipes by importing the angular2-moment library.
Note 2: For each item with a link, the entire URL is passed through it’s
url attribute. To only show the link domain, I created a pipe called
domain. Take a look here for the code.
Now if you run the application, you’ll see the first page of Hacker News! Click here for the full source code until this step.
Things are kinda slow though
Let’s take a look at the requests transferred when we load the front page of our application.
Woah, 31 requests and 20.8KB transferred in 546ms. This takes almost five times as long loading the front page of Hacker News and more then twice as much data to just load the posts. This is pretty darn slow, and maybe it’s kind of tolerable when you’re loading the list of posts on the front page but this is a serious problem if we try loading a large number of comments for a single post.
I built the entire application with each component using this method, including each post and their comments. You can take a look at what happens when I try to load a post with almost 2000 comments here. But to save you time from watching that entire gif, it takes 741 requests, 1.5MB and 90s to load roughly 700 of the comments (I wasn’t patient enough to wait for every comment to load).
Just for reference’s sake, I still have this version of the app up on my GitHub pages. At your own caution, you can take a look at how long it takes to load this many comments here.
Let’s switch things up
Okay, now we can see why having multiple network connections to fetch a parent item and it’s content isn’t the nicest experience. After a little bit of searching, I found this awesome unofficial API which returns an item and it’s details through a single request.
For example, the response for the list of top stories looks like this.
Notice that there is a
domain as well as a
time_ago attribute which is pretty cool. This means we can ditch the
domain.pipe.ts file I created earlier as well as uninstall the
angular2-moment library. Let’s take a look at what we need to change in our data service.
Since the API doesn’t load all 500 top stories, we need to add page number as an argument. Notice how we’re also passing
storyType as well. This will allow us to show different types of stories depending on where the user navigates to.
Let’s take a look at how we can change the stories component. We can start with just passing in
'news' and page number
1 into our service call to get our top stories.
The correpsonding markup is as follows.
Since all our item components are not loading individually async anymore, we set up the loading section (where we can have a loading indicator) here. Moreover, we just pass in the item object of each post to the child item component.
This means we should be able to clean things up in
item.component.ts, we don’t need to inject
HackerNewsService anymore and our component is now simply a conduit to take in the item object from it’s parent.
The markup (
item.component.html) is very similar, but we now don’t need to conditionally check if the item object is present anymore (we do that in the parent component). Moreover, each parameter now refers to the properties of our new API.
Now let’s see what happens when we run this bad boy.
And now everything loads much faster. The source code for this step can be found here.
We’ve come quite a long way, but before we continue let’s map out the entire component structure of the application. Please excuse my funky Powerpoint skills.
Let’s start with what we’ve built so far.
Let’s also map out the components that show when we navigate to the comments page.
To allow the user to navigate between these pages, we’re going to have to include some basic routing in our application. Before we begin, let’s create our next component.
Now let’s create an
app.routes.ts file in our
An overview of what we’re doing here:
- We just created an array of routes, each with a relative path that maps to a specific component
- Our header navigation links will route to a number of different paths;
jobs. All these paths will map to our
- We’ve set up our root path to redirect to
newswhich should return the list of top stories
- When we map to
StoriesComponent, we pass down
storiesTypeas a parameter through the
dataproperty. This lets us have a story type associated for each route (we’ll need this when we use our data service to fetch the list of stories)
:pageis used as a token so that
StoriesComponentcan fetch the list of stories for a specific page
:idis similarly used so that
ItemCommentsComponentcan obtain all the comments for a specific item
There’s a lot more you can do with routing, but this basic setup should be everything we need. Now let’s open
app.module.ts to register our routing.
To tell Angular where to load the component to route to, we need to use
Let’s bind our navigation links in
HeaderComponent to their respective routes.
RouterLink directive is responsible for binding a specific element to a route. Let’s update the
Let’s parse out what we’ve added. First of all, we imported
ActivatedRoute which is a service that allows us to access information present in the route.
We then subscribe to the route data property and store
storiesType into a component variable in the
ngOnInit hook. Notice how we assign any type to the response object. This is just a quick and simple way to opt-out of type checking. Otherwise you may see an error that states property
storiesType does not exist.
And finally, we subscribe to the route parameters and obtain the page number. We then fetch the list of stories using our data service.
To signal completion, we use
onCompleted() to update a
listStart variable which is used as the starting value of our ordered list (which you can see in the markup below). We also scroll to the top of the window so the user is not stuck at the bottom of the page when he/she tries to switch pages.
We now have the front page complete with navigation and pagination. Run the application to see the good stuff.
We’re almost done! Before we start adding our other comment page components, let’s update the links in
ItemComponent to include routing.
Run the application and click on an item’s comments.
Beauty. We can see that it’s routing to
ItemCommentsComponent. Now let’s create our additional components.
We should add a new
GET request to our data service to fetch comments, so let’s do that before we start filling our components in.
And now we can fill out our components.
Similar to what we did in
StoriesComponent, we subscribe to our route parameters, obtain the item id and use that to fetch our comments.
At the top of the component, we’re going to display the item details, followed by it’s description (
item.content). We then input the entire comments object (
app-comment-tree, the selector for
CommentTreeComponent. The styling for this component can be found here.
Next, set up the
Nice and simple, we list all the comments using the
ngFor directive. Click here to see it’s SCSS file.
Let’s fill out
CommentComponent, the component responsible for each specific comment.
Notice how we’re recursively referencing
app-comment inside of it’s own component. This is because each comment object in the array has it’s own array of comments, and we’re using recursion to show all of them.
Click here to see the styling for this component. If you now run the application, you can see all the comments for each item!
The entire source code for this step can be found here.
All we have left is user profiles. Since the concept is pretty much the same, I won’t go through this in detail. All you need to do is:
- Set up another request in the data service to point to the user endpoint
- Create a user component
- Add another field to your routes file
- Update the user links in the other components to route to the user
And that’s it! Take a look here if you want to see the whole user component setup.
Wrapping things up
We’re done! To kick off a production build, you can run
ng build --prod or
ng serve --prod which will make use of uglifying and tree-shaking.
If you happen to be interested enough to work on this app further, take a look at the issue list and feel free to put up a feature request or a PR! There have been some structural and feature changes since I’ve written this post and this version of the app lives in a separate branch from master, so you’ll need to go there to see the latest version .
If you’re interested, please take a look at my post on how I made this a Progressive Web App that works offline and can be installed to your mobile homescreen!