Building a Hacker News Clone in AngularJS – Part 3

tutorial

This is Part 3 of the series on Building a Hacker News Clone in AngularJS. The Project is the penultimate assignment in the Learn Verified Full Stack Web Developer Program. In this post, we’ll be focusing on implementing AngularJS Pagination. To do this, we’ll be using a library developed by Michael Bromley.

In this post we’ll cover the following tasks:

  • Using the dir-paginate directive to paginate our list of top stories
  • Using the dir-pagination-controls directive to add pagination links.
  • Displaying the proper numbers on each page

Michael Bromley’s library gives us two directives that we can use to implement AngularJS Pagination. The library itself makes it extremely easy to add pagination to your Angular app. In the following sections, you’ll see that we ran into some issues that are specific to our Hacker News Clone. After we add pagination to our app, we’ll walk through how to work with the library to address the issues that were specific to this application.

Adding Pagination to our Top Stories Page

The basic concept of pagination in this project is to slice up our array of 500 stories into pages of 30 stories each. The reason we do this is to make it easier for our page to load (if we had any kind of imagery here, the load of processing 500 images would be significantly different from the load of 30 images). So, we paginate for the sake of performance. But what if we’re sorting alphabetically and we’re looking for a record that starts with ‘L’. We’re going to have to either guess what page that record is on, or, hopefully we’ve got some sort of search to filter the list.

If we want to extend this application and add searching and filtering, it’s easy to see how our implementation of pagination could become much more complicated. This is especially true if we were to roll our own pagination in the controller. If we did this, we could add properties to keep track of the current page, how many items we want per page, and how many items there are in the list. Then we would add a number of links to navigate through the pages that would display different slices of the array. But, what if we filter the list? Now there aren’t as many results. We also have to update the pagination controls and make sure we adjust the number of pages.

What to do about pagination in AngularJS

So, when I started looking into ways of adding pagination to this top stories view, I ended up coming across a library developed by Michael Bromley for AngularJS Pagination. This library is designed to be a plug and play solution for pagination in AngularJS. In order to do this, the library creates a custom directive attribute dir-paginate that uses ng-repeat under the hood, extending its functionality by adding pagination. The library also adds another directive element dir-pagination-controls which creates the pagination links using a template that you’ll be able to edit.

Let’s get started adding the library to our Hacker News Clone. First, we’ll need download the files we need. Drop into the terminal and enter the following command:

npm install angular-utils-pagination

And now we can inject the module as a dependency in our app.js file:

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

Finally, let’s add a script tag in our index.html to include the directive (defined as a module) in the library:

<!-- index.html -->
  ...
  <script src="node_modules/angular-utils-pagination/dirPagination.js"></script>
  <script src="js/app/app.js"></script>
  ...

At this point I’ll load up the local server again and make sure I’m not getting any errors. If it worked, everything should look the same as before.

Adding the dir-paginate directive for AngularJS Pagination

Okay, so let’s see how we can use the new directive to paginate our top stories page. To get an idea of how to do this, the documentation is a very good place to start. The dir-paginate directive expects an expression that matches the syntax of the ng-repeat directive. The only difference is that dir-paginate requires that you include the itemsPerPage filter. Because, the pagination logic requires access to the value of itemsPerPage to do its work.

All right, so let’s open up our top-stories view, switch out our ng-repeat for dir-paginate and add the itemsPerPage filter and set it to 30:

<!-- views/top-stories.html -->
<h2>Here are the top stories</h1>
<ol>
    <li dir-paginate="story in vm.stories | itemsPerPage: 30">
        <story id="story"></story>
    </li>
</ol>

Now, let’s check it out in our browser. Run node server.js and open up localhost:8080 and let’s see what we get:

Hacker News Clone, AngularJS Pagination 30 stories on a Page

Great! But, how do we see story number 31? Let’s add the other directive that the pagination library creates for us.

Adding the dir-pagination-controls directive to add pagination links

To add the pagination links below our top stories, let’s add in the dir-pagination-controls directive. In this instance, the directive behaves as a custom element. We can pass the following attributes to our dir-pagination-controls element: max-size (optional, default = 9), direction-links (optional, default = true), boundary-links (optional, default = false), on-page-change (optional, default = null), pagination-id (optional), template-url (optional, default = directives/pagination/dirPagination.tpl.html), auto-hide (optional, default = true). See the documentation’s section on dir-pagination-controls for more info.

For our example, let’s just add the default pagination controls and try them out. Open up the top-stories view and add the <dir-pagination-controls> directive at the bottom:

<!-- views/top-stories.html -->
<h2>Here are the top stories</h1>
<ol>
    <li dir-paginate="story in vm.stories | itemsPerPage: 30">
        <story id="story"></story>
    </li>
</ol>
<dir-pagination-controls></dir-pagination-controls>

Now let’s load up the page in our browser again and see how it looks!

Hacker News Angular JS Pagination Numbers Not Working

If we weren’t using an ordered list here, we would be done. Michael Bromley’s library makes it that easy to implement AngularJS Pagination. In our case, it’s working great, but we’re having a problem with the numbers. Each page lists the stories from 1-30. They’re different stories, but we don’t know what number belongs to which story (numbers 31, 61, 91, etc are all labeled 1). This is because we’re displaying the stories in an ordered list and each page has 30 items in the list.

If you’re in a hurry reading this, feel free to skip these next sections and go to the final solution. In the next section I talk about a solution to this problem using CSS and JavaScript’s indexOf method to fill in the correct number. I did it this way because I wasn’t sure at first how to use the start property on an ordered list to fix the numbering issue. I think using the start property is a better solution, so feel free to skip to that section.

The way we’re going to fix this is to make a couple of changes. First, we’re going to use a CSS library called tachyons, so let’s going to add a link to it in my index.html file:

<!-- index.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="https://npmcdn.com/tachyons@4.0.1/css/tachyons.min.css"/>
    <link rel="stylesheet" href="app.css" />
    
</head>
...

Next, we’re going to change the <ol> in the top-stories view to a <ul> and give it the class list:

<!-- views/top-stories.html -->
<h2>Here are the top stories</h1>
<ul class="list">
    <li dir-paginate="story in vm.stories | itemsPerPage: 30">
        <story id="story"></story>
    </li>
</ul>
<dir-pagination-controls></dir-pagination-controls>

And now let’s take a look:

Hacker News Clone AngularJS Pagination No Numbers

Okay, but now we don’t have any numbers at all! What we’re going to do here is to insert the numbers programatically. We’re feeding in vm.stories to the ngRepeat directive (still active under the hood of dir-paginate), so we can use the indexOf method to see which story is being displayed. Once we know that, we can add 1 to it and we’ll have the number we want.

<!-- views/top-stories.html -->
<h2>Here are the top stories</h1>
<ul class="list pl0">
  <li dir-paginate="story in vm.stories | itemsPerPage: 30">
    <div>
    	{{ vm.stories.indexOf(story) + 1 }}. 
    </div>
    <story id="story"></story>
  </li>
</ul>
<dir-pagination-controls></dir-pagination-controls>

Now when we load up the page in the browser we’ve got the numbers that we’d expect:

Hacker News Clone Angular JS Pagination with Numbers (but funny looking)

Wait a minute, now we’ve got the right numbers but they’re on their own line! We want them on the same line as the story title. To do that, we’re going to need to add some tachyons classes to get everything lined up. First, I want to extend tachyons with some additional classes for adjusting the width of elements. To do that, I’m going to add a new css file and link it to my index.html:

// css/tachyons-extras.css
.w-5 { width: 5%; }
.w-90 { width: 90%; }
.w-95 { width: 95%; }

@media screen and (min-width: 30em) {
	.w-5-ns  { width: 5%; }
	.w-90-ns  { width: 90%; }
	.w-95-ns  { width: 95%; }
}

@media screen and (min-width: 30em) and (max-width: 60em) {
  .w-5-m  { width: 5%; }
	.w-90-m  { width: 90%; }
	.w-95-m  { width: 95%; }
}

@media screen and (min-width: 60em) {
  .w-5-l  { width: 5%; }
	.w-90-l  { width: 90%; }
	.w-95-l  { width: 95%; }
}

These styles follow the same pattern as tachyons, allowing a couple more options for percentage width. And I’ll add the link to the head of my index.html file:

<link rel="stylesheet" href="css/tachyons-extras.css" />

Okay, now that we’ve got that done, I want to add some classes so we can get these numbers lined up. First, let’s talk about why the numbers are on their own line right now. A <div> is a block element, so it will stack vertically with other block elements unless it is told otherwise. So, let’s tell the <div> elements that contain our story and its number to display side by side using the float property. To add float: left to these divs, we add the tachyons class fl

<!-- views/top-stories.html -->
<h2>Here are the top stories</h1>
<ul class="list">
  <li dir-paginate="story in vm.stories | itemsPerPage: 30">
    <div class="fl">
    	{{ vm.stories.indexOf(story) + 1 }}. 
    </div>
    <div class="fl">
    	<story id="story"></story>
    </div>
  </li>
</ul>
<dir-pagination-controls></dir-pagination-controls>

After we’ve made those changes in the two views, this is what we’ve got:

Hacker News Clone AngularJS Pagination Numbers all wonky

Oh my! Now the numbers are all over the place. Well, actually, it seems like the numbers for the next story are going at the end of the story title and domain, not below like we would want. There’s also another problem. Now that all of the elements within our <li> are floated, if we scroll down to the bottom of the page we’ll see that our pagination has disappeared. To fix this, we need to add a clearfix to our <li> tags so that they have height within our layout and everything behaves as expected. To do this, we add the tachyons class cf to our <li> in that has our dir-paginate directive:

<!-- views/top-stories.html -->
<h2>Here are the top stories</h1>
<ul class="list">
  <li class="cf" dir-paginate="story in vm.stories | itemsPerPage: 30">
    <div class="fl">
    	{{ vm.stories.indexOf(story) + 1 }}. 
    </div>
    <div class="fl">
	<story id="story"></story>    
    </div>
  </li>
</ul>
<dir-pagination-controls></dir-pagination-controls>

And now when we open the page in our browser, we can see that it’s looking much better:

Hacker News Clone AngularJS Pagination After Clearfix

Still, it’s not quite right yet. Check out number 22. It’s on its own line like they all were before. The reason for this is that this story has an especially long title and the two divs won’t fit on the same line together. So, in order to make sure that both divs will fit on the same line, we can specify widths for the divs and make sure that they won’t add to more than 100% of the width of their container. To do that, let’s add some tachyons classes to the divs:

<!-- views/top-stories.html -->
<h2>Here are the top stories</h1>
<ul class="list">
  <li class="cf" dir-paginate="story in vm.stories | itemsPerPage: 30">
    <div class="fl w-10">
    	{{ vm.stories.indexOf(story) + 1 }}. 
    </div>
    <div class="fl w-90">
	<story id="story"></story>    	
    </div>
  </li>
</ul>
<dir-pagination-controls></dir-pagination-controls>

After we’ve made that change, let’s take a look at the page in our browser:

Hacker News Clone Numbers Too far away

Okay, now everything is lining up properly. But, the numbers are quite far away from the stories. Because we’ve defined these widths, we know that the numbers and the stories will display on the same line. But, we don’t need so much space for the div with the number in it. What can we do to limit the space this div takes up? Well, let’s give that div a max-width. To do that, we’ll add a tachyons class called mw2:

<!-- views/top-stories.html -->
<h2>Here are the top stories</h1>
<ul class="list">
  <li class="cf" dir-paginate="story in vm.stories | itemsPerPage: 30">
    <div class="fl w-10 mw2">
    	{{ vm.stories.indexOf(story) + 1 }}. 
    </div>
    <div class="fl w-90">
	<story id="story"></story>    	
    </div>
  </li>
</ul>
<dir-pagination-controls></dir-pagination-controls>

And now let’s see what we’ve got when we open the page in the browser:

Hacker News Clone Top Stories after applying mw2 class

Okay, now we’re talking. This is starting to look right now. There are just two more tweaks I want to make and I think we’ll have it just right. First, I think the list is indented from the left a bit much. This is because of the left padding added to the <ul> tag, so let’s take that away by adding the tachyons class pl0. Next, I think the numbers would look better up against the story titles if they were aligned to the right instead of the left (so the periods all line up). To change that, we can add the tachyons class tr (text align right):

<!-- views/top-stories.html -->
<h2>Here are the top stories</h1>
<ul class="list pl0">
  <li class="cf" dir-paginate="story in vm.stories | itemsPerPage: 30">
    <div class="fl w-10 mw2 tr">
    	{{ vm.stories.indexOf(story) + 1 }}. 
    </div>
    <div class="fl w-90">
	<story id="story"></story>    	
    </div>
  </li>
</ul>
<dir-pagination-controls></dir-pagination-controls>

And now let’s take a look:

Hacker News Clone Numbers too Close

Almost! I think it looks like the numbers are just a bit too close to the story titles. So, let’s add in a bit of padding on the right to fix this. To do this, we add the tachyons class pr1:

<!-- views/top-stories.html -->
<h2>Here are the top stories</h1>
<ul class="list pl0">
  <li class="cf" dir-paginate="story in vm.stories | itemsPerPage: 30">
    <div class="fl w-10 mw2 tr pr1">
    	{{ vm.stories.indexOf(story) + 1 }}. 
    </div>
    <div class="fl w-90">
	<story id="story"></story>    	
    </div>
  </li>
</ul>
<dir-pagination-controls></dir-pagination-controls>

Let’s take one last look:

Hacker News Clone Perfect

Finally, that’s looking great!

Although it took a while to get this looking right, this will hold up to any changes we may make in the future because it’s very easy to see where all the information is coming from.

Moving on, we’ll want to add our changes and commit them to our repository:

git add . 
git commit -m "adds pagination to top stories page"

Still, I couldn’t help but feel that this was a bit much just to solve that numbering problem. My curiosity got the best of me, so I ended up finding another way to fix the numbering solution.

Another Possible Solution

The first alternate solution I found came from digging into the source code in dirPagination.js. It involves adding one line to the dir-pagination-controls directive’s link function. Basically, it changes the start property on an ordered list in the top-stories view depending on what the value of the currentPage variable is. When we click on one of the pagination links, the value of the currentPage variable changes, and we can adjust the start property on the <ol> tag to start the list at the appropriate number. All of this can be done in one line of code, added to the scope.setCurrent function within the dirPaginationControlsLinkFn:

 // node_modules/angular-utils-pagination/dirPagination.js lines 329-335 as of this writing
 scope.setCurrent = function(num) {
  if (paginationService.isRegistered(paginationId) && isValidPageNumber(num)) {
    num = parseInt(num, 10);
    paginationService.setCurrentPage(paginationId, num);
    element.parent().find('ol')[0].start = (num - 1) * 30 + 1;
  }
};
 

While this solution is only one line of code, it presents a lot of problems. First, it relies on the structure of the top-stories.html view remaining the same. If we added another ol to the page, this code would break. If we added a wrapping div around the <dir-pagination-controls> element to the page, this code would break. If we added a wrapping div around the <ol> tag on the page, this code would break. If we happened to update the dirPagination npm package, this code would break (and disappear!). And, when it broke, it wouldn’t be very obvious why. We’d have to dig into the console, read the error message from angular, trace the error back to the dirPagination.js file, go hunting for that file in our project, make an adjustment to it and hope for the best:

AngularJS pagination console error after dir-pagination hack breaks

In the end, modifying the code in one of our dependencies to solve a problem we’re having in a particular use case is just a bad idea.

So, while this solution is quicker (if you think about it first), it’s much more brittle and likely to be super annoying down the road. That said, looking for these kinds of solutions can be really informative when it comes to learning about our dependencies. Looking under the hood of a library that we’re using can give us a much better sense of how the code fits into our project.

Still, I couldn’t kick that feeling… There’s gotta be a better way.

There’s gotta be a better way!

Okay, so if you still feel like there must be a better way to fix this numbering issue, you’re right. I went through all of this so you don’t have to. It turns out, there are a couple of options that we can use with the dir-paginate and dir-pagination-controls directives to fix the numbering issue without having to touch any CSS.

One of the main habits to learn while becoming a programmer is to always think about how our code is organized. Specifically, what is the code we’re writing for? The answer to this question can guide us to putting the code in the right place.

In this case, I couldn’t help but feel that having to mess with the CSS and create new elements just to add the correct numbers to my list was not the right approach. I knew it would look much better if we could still use an ordered list somehow. As you can see, I’m still learning to trust those instincts and to stop and do a stuck check. It’s important to trust that instinct that says: “There’s gotta be a better way!”

When I get that feeling, I try to take a step back and make sure there isn’t already a simpler solution to the problem I’m trying to solve. I try to think about what I’m trying to do and to formulate a google search. This time, I actually came across a better solution by re-reading part of this blog post! I noticed the on-page-change option for the dir-pagination-controls directive that I’d described but not really looked into…

The Better Way

Anyway, after some digging, I found there is one main option built into the dir-pagination-controls directive that we can utilize to solve our problem. That option is called on-page-change. The option allows us to pass a callback function (defined on our controller’s $scope) that will run whenever the page changes. The function accepts two parameters: newPageNumber and oldPageNumber. We can change our list back to an ordered list and use this newPageNumber parameter to update the start property of our list depending on the current page.

I’ll also go through how we can add dynamic urls to keep track of our pagination using Angular UI Router. That way, we can navigate through the pages using the pagination links and our browser’s URL will update accordingly. And, we can remove all of our unnecessary styles.

So, in the following section we’ll cover the following tasks:

  • Defining an updatePage method on our controller’s $scope
  • Adding the on-page-change option to our dir-pagination-controls to call it
  • Creating a property called vm.start to keep track of the appropriate starting number for a certain page of our list.
  • Binding the vm.start property to our ol tag in our top-stories view.
  • Adding a parameter to our 'top' state defined in app.js to allow us to pass in the current page.
  • Adding a currentPage property to our scope and binding it to our dir-paginate directive so we display the correct data for the current page. (We should see stories 61-90 if we load localhost:8080/#/top?page=3 not stories 1-30 labeled as 61-90)
  • Using Angular’s $location service within the updatePage method to update the URL when we change pages.

Defining the updatePage Method and Calling It from dir-pagination-controls

Okay, so the first the thing we need to do before we can add the updatePage method to our controller’s scope is to inject $scope as a dependency. Since we’ve been using controllerAs syntax, we haven’t been using our controller’s $scope object yet. Next, let’s add the updatePage method to our controller’s scope:

// js/app/controllers/TopStoriesController.js
...
  TopStoriesController.$inject = ['TopStoriesService', '$scope'];
  function TopStoriesController(TopStoriesService, $scope) {

    var vm = this;
    

    activate();

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

    function activate() {
      vm.stories = [];
      $scope.updatePage = updatePage;

      TopStoriesService 
        .getStories()
        .then(function(res) {
          vm.stories = res.data;
        });
        
      function updatePage(newPageNumber, oldPageNumber) {
        console.log(newPageNumber);
      }

    }
  }

Adding the on-page-change option to our dir-pagination-controls

Okay, so now that we have this method defined, let’s make sure we’re calling it when the page changes. To do this, we’ll pass a reference to this function to the on-page-change option on our dir-pagination-controls directive.

<!-- top-stories.html --> 
...
<dir-pagination-controls on-page-change="updatePage(newPageNumber, oldPageNumber)"></dir-pagination-controls>

Now, when we load the page in our browser and click on a couple of the pagination links, the current page number is logged to the console.

Hacker News Clone on Page Change Function is being called

Here you can see that the value of the current page is being logged to the console, as expected.

Remember that it’s important that our updatePage function take the particular arguments: newPageNumber and oldPageNumber. The function won’t work otherwise. All right, let’s keep going!

Setting the vm.start property within our updatePage callback

Now that we have the current page number, we can calculate the number that our list should start on. There are 30 items on each page and the first page should start on 1. So, the start property of our list should be equal to 30 * (pageNumber – 1) + 1. But, how do we get this information back to our top-stories view so that we can adjust the starting point of the list? Well, we’re going to define a property vm.start on our view model object vm from within our controller. Once we’ve done that, we can pass this starting value to the view. There, we can use it to set the start property of our list (after we change it back to an ordered list!).

// js/app/controllers/TopStoriesController.js
  ...
  function activate() {
      vm.stories = [];
      $scope.updatePage = updatePage;
      vm.start;

      TopStoriesService 
        .getStories()
        .then(function(res) {
          vm.stories = res.data;
        });
        
      function updatePage(newPageNumber, oldPageNumber) {
        vm.start = 30 * (newPageNumber - 1) + 1;
        console.log(vm.start); 
      }

    }

When we reload the page in the browser and use the pagination links, we can see the values of vm.start logged to the console:

Hacker News Clone Setting the Proper Vm start Property

Here, we can see the correct vm.start values logged to the console.

Now that we’ve got the vm.start property working with our on-page-change callback, let’s commit this change and move on to the next step:

git add . 
git commit -m "adds vm.start property to TopStoriesController"

Binding the vm.start property to the ol tag in our top-stories view

First, we need to remove the tachyons classes we added earlier and switch the top-stories list back to an <ol> tag from a <ul>. We can also remove the two divs within our list items because the ordered list is going to handle our numbering:

<!-- top-stories.html -->
<h2>Here are the top stories</h2>
<ol>
  <li dir-paginate="story in vm.stories | itemsPerPage: 30">
    <story id="story"></story>
  </li>
</ul>
<dir-pagination-controls on-page-change="updatePage(newPageNumber, oldPageNumber)"></dir-pagination-controls>

Okay, so now all we have to do is adjust line 3 to set the start property equal to the vm.start value that we’re setting and updating each time the page changes:

<ol start="{{ vm.start }}"> 

Now, when we reload the page in the browser:

Hacker News Clone Binds vm.stat to ol.start property

Here we can see that the numbering is working as expected after binding the ol tag’s start property to vm.start

So, we’ve got the numbering working and looking great in much less time. And, we did it the Angular way! Beyond that, this solution also offers us a bunch more flexibility and power. Right now, when we reload the page, we go back to the beginning of the list. What if we wanted to be able to link to different pages on the list? In the next few steps we’ll go through how we can get Angular UI Router involved to help us do just that. But, when we click on pagination links, the URL doesn’t change–which is inconsistent with dynamic routes for different pages. We’ll also go through how we can use Angular’s $location service within the our updatePage callback to update the URL when we click on pagination links.

But, before we do anything further, let’s add our changes and commit them to our repository.

git add .
git ci -m "binds vm.start property to ol stag in top-stories view"

Adding a parameter to our 'top' state to allow URLs for different pages

If we want to add a URL component to our pagination, we can do so using Angular UI Router. To add a parameter, we can edit the configuration object that we’re passing to the top state. To avoid running into problems when the parameter is not present, let’s adjust the url property on the 'top' state to be /top. Specifically, we can add ?parameterName within the url property of this object and we’ll be access the value of this parameter by using $stateParams.parameterName from within our TopStoriesController. We’ll also add a default route of /top by injecting Angular Ui Router’s $urlRouterProvider into our app’s config function. After we add $urlRouterProvider, we can define an otherwise() function on it and pass '/top' as a parameter. Once we’ve done that, UI Router will know to redirect us to the top state if it can’t find a match for the URL we were looking for:

 
// js/app/app.js
(function() {
'use strict';

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

Now, we’ll be able to visit urls like localhost:8080/#/top?page=3 and we’ll still see the top stories page. At the moment, though, it will still display the first 30 stories. This is because the dir-paginate directive doesn’t know that we want to see page 3. Fortunately, the dir-paginate directive has an option that we can use to specify the current page. Namely, current-page. We can pass the current-page option an expression that returns the current page we want to display.

Adding a currentPage property to our scope and binding it to the current-page option on our dir-paginate directive

In our case, we can create a property on our controller’s scope, $scope.currentPage, and update its value whenever the $stateParams.page is passed via the URL. This way, the dir-paginate directive will know to display page 3 of our top stories list when we visit localhost:8080/#/top?page=3. First, we need to pass $stateParams as a dependency to our TopStoriesController. Then, we’ll be able to access the value of the ?page parameter we defined in our top state in the previous example:

// js/app/controllers/TopStoriesController.js
  ...
  TopStoriesController.$inject = ['TopStoriesService', '$scope', '$stateParams'];
  function TopStoriesController(TopStoriesService, $scope, $stateParams) {
    var vm = this;
    

    activate();

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

    function activate() {

      vm.stories = [];
      $scope.currentPage = $stateParams.page || 1;
      $scope.updatePage = updatePage;
      vm.start;
      
      TopStoriesService 
        .getStories()
        .then(function(res) {
          vm.stories = res.data;
        });

      function updatePage(newPageNumber, oldPageNumber) {
        vm.start = (newPageNumber - 1) * 30 + 1;
      }

    }
  }

Here, we’re setting $scope.currentPage to be equal to the value of $stateParams.page, if there is such a value. For example, if we visit localhost:8080/#/top?page=3, the value of $stateParams.page would be 3, and $scope.currentPage would be set equal to 3. If, however, we just visit localhost:8080, $stateParams.page would be undefined. So, we use the || operator to specify that $scope.currentPage should be 1 (the first page) in that case. So, all that’s left to do here to get this working is to pass this $scope.currentPage value that we’ve defined in the controller into our dir-paginate directive in the view. Here’s how we do that:

<!-- top-stories.html -->
<h2>Here are the top stories</h2>
<ol start="{{ vm.start }}">
  <li dir-paginate="story in vm.stories | itemsPerPage: 30" current-page="currentPage">
    <story id="story"></story>
  </li>
</ol>
<dir-pagination-controls on-page-change="updatePage(newPageNumber, oldPageNumber)"></dir-pagination-controls>

Now, when we load localhost:8080/#/top?page=3 in the browser, we’ll see the third page of results. However, they will be numbered 1-30! The reason for this is that we’re updating the value of vm.start within the updatePage callback function. But, this callback is only called when we click on the pagination links, not when we visit a page directly from the url. So, we need to initialize our vm.start property in our controller so that it matches the current page reflected in our url:

// js/app/controllers/TopStoriesController.js 
  ... 
  TopStoriesController.$inject = ['TopStoriesService', '$scope', '$stateParams'];
  function TopStoriesController(TopStoriesService, $scope, $stateParams) {

    var vm = this;
    

    activate();

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

    function activate() {
      vm.stories = [];
      $scope.currentPage = $stateParams.page || 1;
      $scope.updatePage = updatePage;
      vm.start = 30 * ($scope.currentPage - 1) + 1;

      TopStoriesService 
        .getStories()
        .then(function(res) {
          vm.stories = res.data;
        });
        
      function updatePage(newPageNumber, oldPageNumber) {
        vm.start = 30 * (newPageNumber - 1) + 1;
      }

    }
  }

Okay, let’s take a look at our output when we visit localhost:8080/#/top?page=3:

Hacker News Clone Dynamic Routes for Top Stories Pages

Here, you can see that we get the top stories numbered 61-90 when we load page 3 at localhost:8080/#/3

This is great so far! Now, we can visit a dynamic url that will take us to specific pages in our top stories list. But, there’s one small hiccup left. When we click on the pagination links, our url doesn’t change! This isn’t right! We want to be able to send a link to a friend, have them click it and see the same thing that we do. How can we do that if the pagination links don’t update the urls? Well, we can’t! So, in the next step, we’ll walk through how to use Angular’s $location service to update our url without reloading the page. But first, let’s add these changes and commit them to our repository.

git add . 
git commit -m "adds dynamic routes for different pages of the top stories list"

Using Angular’s $location service to update the URL when the page changes

In this section, we’re going to add Angular’s $location service to our TopStoriesController. Then, we’ll use it within our updatePage callback function so that our URLs always match up with the current page we’re on. Fortunately, Angular makes this pretty easy to do. First, we’ll handle our dependency injection by adding $location to our TopStoriesController. Next, we’ll go into our updatePage callback and add a call to $location.url(), passing in the proper url as an argument using the newPageNumber:

// js/app/controllers/TopStoriesController 
  ...
  TopStoriesController.$inject = ['TopStoriesService', '$scope', '$stateParams', '$location'];
  function TopStoriesController(TopStoriesService, $scope, $stateParams, $location) {

    var vm = this;
    

    activate();

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

    function activate() {
      vm.stories = [];
      $scope.currentPage = $stateParams.page || 1;
      $scope.updatePage = updatePage;
      vm.start = 30 * ($scope.currentPage - 1) + 1;

      TopStoriesService 
        .getStories()
        .then(function(res) {
          vm.stories = res.data;
        });
        
      function updatePage(newPageNumber, oldPageNumber) {
        $location.url('top?page=' + newPageNumber);
        vm.start = 30 * (newPageNumber - 1) + 1;
      }

    }

Now, when we load the page in our browser, we can navigate freely between the pages, using either urls or the pagination links, and everything stays in sync! Let’s make our final commit for this section:

git add . 
git commit -m "adds $location service to update url on clicking pagination links"

Final Thoughts

Looking back, most of the time we spent adding pagination was actually solving the style issue that we had because we wanted to display the numbers ascending from 1 to 500. Michael Bromley’s library makes it very easy to add AngularJS pagination to our application. I hope that this article has given you an idea of how powerful this AngularJS pagination directive is and how you might be able to use it in some of your future projects. In the next post, we’ll cover adding dynamic routes to our application using Angular Ui router. We’ll use these new dynamic routes to create a view that will display all the comments for a given story.

Leave a Reply

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