Building a Hacker News Clone in AngularJS – Part 1

tutorial

As part of the curriculum for the AngularJS portion of the Learn Verified Full Stack Web Developer Course, students build a Hacker News Clone. In this project, we use the Hacker News API to reconstruct the top stories page at Hacker News.

At this point, we’ve already completed nearly the entire course, which I’ve been working on part time for the past 8 months, so we’re pretty far in. Of the 600+ lessons I’ve completed, 110 of them are related to AngularJS. We’ve already covered a lot of the concepts that we’ll be tackling when building this project, but up until this point we’ve been working on them one small piece at a time. In the next few posts, I’ll be walking you through my progress through the project.

In my first post about building a Hacker News Clone in AngularJS, I’m going to focus on the following tasks:

  • Setting up the directory structure
  • Adding necessary libraries and linking them to the project
  • Bootstrapping the angular app with ui-router
  • Building the top stories state, with root url, a view and a controller
  • Creating the top-stories view and the TopStoriesController and linking them to index.html
  • Creating a TopStoriesService to connect to the Hacker News API
  • Using an ng-repeat to display ids of the hacker news top stories

Setting up the Directory Structure

The first thing I’m going to do is create my directory structure. I need to add a js folder for the libraries I’m going to be using and a js/app folder for my angular code: js/app/controllers/, js/app/services and app.js. I’ll also make a folder for my views. From the root of the project, I can run these commands to create this structure:

mkdir js
mkdir js/app
mkdir js/app/controllers
mkdir js/app/services
touch js/app/app.js
mkdir views

Now that we’ve got the bones in place, let’s add our changes and commit them to version control:

git add . 
git commit -m "sets up directory structure"

Adding Necessary Libraries and Linking Them to the Project

All right, now we need to add the libraries we’re using into our js folder as well. To create this Hacker News Clone, we’ll be using the following libraries:
Angular.js
Angular-ui-router.js &
Angular-sanitize.js

In order to add these libraries to the project, you can cd into the js directory and then use curl to copy the files into the js folder (or you can just visit the links and hit command (or control) + S to save them into the js folder:

cd js
curl https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js > angular.min.js
curl http://angular-ui.github.io/ui-router/release/angular-ui-router.js > angular-ui-router.js
curl https://raw.githubusercontent.com/angular/angular.js/master/src/ngSanitize/sanitize.js > angular-sanitize.js

And then once they’ve been added to the js folder, we can link to them from within the index.html file:

<!-- index.html -->
<!doctype html>
<html>
<head>
	<meta charset="utf-8">

	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title>Hacker News Clone</title>

    <link rel="stylesheet" href="app.css" />
</head>
<body>

<script src="js/angular.min.js"></script>
<script src="js/angular-ui-router.js"></script>
<script src="js/angular-sanitize.js"></script>
<script src="js/app/app.js"></script>
</body>
</html>

Now that we’ve added our libraries. Let’s add the changes and commit to version control. First, though, we need to change directory back into the root of the project:

cd ..
git add . 
git commit -m "adds libraries and links them to index.html"

Making sure Angular is working properly

At this point, I usually like to put in a div with an ng-app directive. Inside that div, I’ll put in an input with an ng-model. Then I’ll add an expression to make sure data binding is working as expected.

<div ng-app>
  <input ng-model="name" />
  <h1>Hello, my name is... {{ name }}</h1>
</div>

We’re going to run our project using node. To do that, we need to create a file in the root of our project called server.js:

touch server.js

Within this file, we need to add a few lines of code:

var express = require('express');
var app = express();

app.use(express.static(__dirname));

app.listen(8080);

console.log('Server started at http://localhost:8080');

Now, we’ll be able to open up the local express server by running the following command:

 
node server.js

The first time we do this however, we’re going to run into an error. This is because we haven’t installed any of the dependencies. So, our app doesn’t know about the module ‘express’. To fix this, we need to create a package.json file. This is the file that node uses to install the dependencies we need. So let’s create a package.json file in the root of our project:

touch package.json

The contents of the repository we were given by Learn to do the project was this:

{
  "name": "testing-controllers",
  "version": "1.0.0",
  "devDependencies": {
    "jasmine-core": "^2.4.1",
    "karma": "^0.13.19",
    "karma-chrome-launcher": "^0.2.2",
    "karma-jasmine": "^0.3.6",
    "karma-spec-reporter": "0.0.23"
  },
  "dependencies": {
    "express": "^4.13.4"
  }
}

To follow along with me, I recommend copying this code and pasting it into your package.json file. After you do that, you can run npm install. This will take a while to run, but when it’s complete, you’ll have a node_modules directory in your project.

But, you don’t want to be tracking all of the contents of the node_modules directory within git. So, we need to add node_modules to the .gitignore file so that git won’t track changes within the node_modules folder.

To do this, we can open up a terminal and type nano .gitignore and hit return. This will open a file called .gitignore within a nano text editor in our terminal. From there, we can type node_modules then type ctrl + O to write the change to the .gitignore file and then ctrl + X to close the editor. Now, we should see that git is no longer paying attention to the node_modules directory.

Okay, so back to the project. Let’s run node server.js again. Now, it should be working. When we load up http://localhost:8080 in our browser we can see that data binding is working:

Angular Data Binding Working

Perfect. So, before we move on, let’s add the .gitignore to our project and commit it to version control:

git add . 
git commit -m "adds gitignore and tests angular functionality"

Great! We can now go about bootstrapping our app.

Bootstrapping our Angular app with ui-router

To start out, let’s bootstrap our Angular App and add a single state for the top stories:

angular
    .module('app', ['ui.router'])
    .config(function($stateProvider) {
        $stateProvider
            .state('top', {
                url: '',
                templateUrl: 'views/top-stories.html',
                controller: 'TopStoriesController as vm'
            })
            
    });

Adding the top-stories view and TopStoriesController

Next, we need to add the view and controller referenced in our new state:

touch js/app/controllers/TopStoriesController.js
touch views/top-stories.html
// js/app/controllers/TopStoriesController.js
(function() {
'use strict';

  angular
    .module('app')
    .controller('TopStoriesController', TopStoriesController);

  TopStoriesController.$inject = [];
  function TopStoriesController() {
    var vm = this;
    

    activate();

    ////////////////

    function activate() {
      vm.stories = ['1', '2']

    }
  }

})();
<!-- views/top-stories.html -->
<h2>Here are the top stories</h1>
<ul>
    <li ng-repeat="story in vm.stories">
        {{ story }}
    </li>
</ul>

And finally, we need to make sure that we update our index.html file. We need to add the “app” value to the ng-app attribute to connect with our module. Next, we need to add a div with the ui-view directive so that Angular UI Router knows where to put our content. Finally, we need to add another script tag at the bottom so that we’re loading our TopStoriesController.

<!-- index.html -->
<!doctype html>
<html>
<head>
	<meta charset="utf-8">

	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title>Hacker News Clone</title>

    <link rel="stylesheet" href="app.css" />
</head>
<body>
	<div ng-app="app">
    <div ui-view></div>
	</div>

  <script src="js/angular.min.js"></script>
  <script src="js/angular-ui-router.js"></script>
  <script src="js/angular-sanitize.js"></script>
  <script src="js/app/app.js"></script>
  <script src="js/app/controllers/TopStoriesController.js"></script>
</body>
</html>

As a result, we can now see the top-stories view when we open http://localhost:8080:

Showing the Top Stories State

Now that we have our controller set up, let’s add our changes and commit them to version control:

git add . 
git commit -m "adds TopStoriesController and top-stories view"

Creating a TopStoriesService to connect to the Hacker News API

Okay, so all of this has been great so far, but now we need to get some actual data into our app. In order to do that, we add a TopStoriesService. Also, we need to add another script tag to the bottom of our index.html file to make sure our app has access to it.

<!-- index.html -->
<script src="js/app/app.js"></script>
<script src="js/app/services/TopStoriesService.js"></script>
<script src="js/app/controllers/TopStoriesController.js"></script>
// js/app/services/TopStoriesService.js
(function() {
'use strict';

  angular
    .module('app')
    .service('TopStoriesService', TopStoriesService);

  TopStoriesService.$inject = ['$http'];
  function TopStoriesService($http) {
    this.getStories = getStories;
    this.getStory = getStory;

    ////////////////

    function getStories() {
      return $http.get('https://hacker-news.firebaseio.com/v0/topstories.json');
    }
    
    function getStory(id) {
      return $http.get('https://hacker-news.firebaseio.com/v0/item/' + id + '.json');
    }
  }
})();

Once we’ve got this service setup, we need to call it from our controller so we can get the data from Hacker News.

// js/app/controllers/TopStoriesController
(function() {
'use strict';

  angular
    .module('app')
    .controller('TopStoriesController', TopStoriesController);

  TopStoriesController.$inject = ['TopStoriesService'];
  function TopStoriesController(TopStoriesService) {
    var vm = this;
    vm.stories = [];
    

    activate();

    ////////////////

    function activate() {
      TopStoriesService 
        .getStories()
        .then(function(res) {
          vm.stories = res.data;
        });

    }
  }

})();

As a result, when we load our local service and visit http://localhost:8080 we can see a long list of story IDs:

Hacker News Clone List of Top Stories Ids

Great! Let’s add our changes and commit them to our repository:

git add . 
git commit -m "adds functions to TopStoriesService to get data from Hacker News API"

In the next posts, we’ll walk through implementing a custom directive to display each story within the view. We’ll be able to call on the getStory function in our TopStoriesService to fetch the information we want and bind it to a view model. Also, we’re going to implement pagination so we can look at 30 stories at a time with links to next and previous. We’ll create a custom component to display individual comments, implementing custom Angular filters to customize our output to the view. Then, we’ll use nested components to display threaded comments. We’ll do a bit more refactoring of our custom filters. And, we’ll wrap up with a discussion of Angular routing resolves and how they might play into our Hacker news Clone.

Continue along with the series by reading Part 2 of my series of posts on Building a Hacker News Clone in AngularJS.

Leave a Reply

Your email address will not be published. Required fields are marked *