How to build more advanced paging with AngularJS

Last week I wrote an article on how to implement simple paging capabilities on a data table with AngularJS (highly recommend you read that article first), today we will further improve our code from last week.

We will implement slightly more advanced paging, with page numbers and the lot! Again, we’ll be using Bootstrap for the front-end markup.

advanced pager
advanced bootstrap pager

The other thing we will be changing is the behaviour of the previous and next buttons when we reach either end of our data set. Before we used the ngHide directive to hide the previous button when there was nothing to go back to in terms of data.

Today, we will change that slightly and disable the previous and next buttons instead of hiding them. To do this, we will use the ngClass directive and apply a class of disabled to our list item for the previous and next buttons.

Note the use of the ngClass directive above. The way this works is simple enough once you understand the syntax. Basically, it is saying if the expression on the right of the colon returns true, then I will apply the class to the left of the colon.

We have now taken care of the ‘looks’ of the previous button, but in terms of functionality, there is a little bit more to do. If it is clicked now, we will still get unexpected behaviour, so we must go ahead and update our previousPage() function too.

Now our previous button not only looks disabled, but also acts it in the sense that if we are on the first page of data, clicking the button would have no effect. Here is what things currently look like:

pager with disabled prev button
pager with disabled prev button

Looking good, let us move on. Pages! We need a way to dynamically generate the list items for each page with correct page numbers and correct behaviour. I know, I know, it sounds complex, but let us think it through together. We know how many items are displayed on each page via our pageSize variable, we also know the total number of items returned from our service.

Is it becoming clear yet? If we divide the total number of items in our returned dataset from our angular service by the pageSize variable, we get to know how many pages we need in order to hold all our data items. Awesome, let us do that then, first we will declare our JavaScript variables at the top of our controller.

Now that we have done that, we must make sure that we set our variables in the correct place within our controller. Remember that our data is coming through via an angular service. When things go well, I have created a success method called successGettingEmployees().

This method is only called if things went well at the service and the API call was a success. It is within this function that we must do our paging calculations. Let’s get going.

Notice the use of the Math.ceil() method. We need this method because we want to round up the number of pages to the nearest integer. Imagine we had a total of 25 data items returned from our service, and our page size is 10. Our above calculation would give us 2.5 pages. We can’t have half a link to half a page! So we round up, meaning we need 3 pages to display all our data.

Awesome, we are making good progress don’t you think? So we now know the exact number of pages we need, that is 3 pages in relation to my returned data. Now then we must tell our view to generate the correct markup in accordance to the number of pages we have.

This is not as easy as you might think. There is no directive in AngularJS were you give it a number (e.g. 3) and tell it to repeat some markup 3 times. So we must first create some sort of array and then hand it over to the ngRepeat directive so that it may loop over the array and generate the markup we require.

But there is a further complication involved! The ngRepeat directive in AngularJS can not loop over primitive types. So you cannot simply create an array of integers like so:

Yep, so we must create JSON objects and put those in our array, and then our repeater would be able to loop over them. Let us do that then!

So above we are looping 3 times, because our ‘totalPageNumbers’ variable holds the number 3, and for each loop, we are creating a simple JSON object that has a property called value which equates to the current loop iteration. We are then adding this newly created JSON object into our pages array on our AbsentEmployees (abEmployees) object on our scope.

Our pages array is now ready to be handed over to the view, where we can use the ngRepeat directive to generate the relevant and correct page listing markup.

Notice how the markup for our previous and next buttons is left outside of the ngRepeat directive, it is nice that we can do this! We are in effect only repeating the buttons for the page numbers in between. Things are looking great! We have a previous button, then the correct number of pages with correct bootstrap markup and then a next button.

Now then let us talk about functions. Functions will handle what happens when each one of the buttons is clicked. The functions that handle our previous and next buttons would remain the same as what they were in the last post I wrote.

But what about the numbered buttons corresponding to the page numbers? Worry not! That too is quite straightforward. We will create a function on our scope, that will take a ‘page’ parameter, remember we called the JSON objects we created ‘page’ when we told our repeater to loop over them.

Once we have the ‘page’ parameter, our function will take the page number by accessing the ‘value’ property on the page parameter and then through some funky logic change our ‘currentPage’ array that holds our 10 current data items. We must also remember to update our offset value so that our previous and next buttons still work as expected since they rely solely on the offset value.

getPage(page) function

Whenever I get into situations like this, I always like to get back to pen and paper and write down what variables need to change and the relationship of change between them.

So let us create a table housing our two most important variables for our scenario. The two important variables are the offset and the page. Here is what the table with its values looks like:

page.value abEmployees.offset
1 0
2 10
3 20
4 30

All the table is saying is, when the page number is 1, abEmployees.offset is 0. And when our page number is 2, abEmployees.offset is 10, and so on. So we have identified this relationship between these two variables. We now need to shift our brain into the mathematics gear and see how mathematically we can deduce the offset from the page number.

Figured it out yet? Awesome! You are right, if we take away 1 from the page number and multiply by 10, we get the offset! Let us try it!

page.value = 1;
var holder = page.value - 1;
// our holder variable now has 0.
abEmployees.offset = holder * 10;
// our offset is 0! awesome! our formula works!

We can now move that logic into our getPage() function, here is what it will look like:

Looking real good there! Next we must update our view and tell it to use this newly created function, we’ll do that by adding an ngClick directive to the anchor tags within our paging list items, our code now looks like this:

This is awesome, and it works! After doing the above, one is able to change the pages via the previous and next buttons, but also the page numbers! A couple of last tiny bits to do. We need to apply the .active CSS class to the list item markup for the active page as detailed on Bootstrap.

bootstrap pager with active page
bootstrap pager with active page

But how can we figure out when to apply the CSS class via code? Well thinking logically about it, we can say the following to ourselves:

When the current offset value matches the offset calculated via the table above for the active page, we want to apply the CSS class.

Great! That wasn’t so bad, we already know our formula for calculating the offset from a given page number, so we just port that over to the expression within our ngClass directive.

Having such a big expression for the ngClass doesn’t look too nice, let us move that out into a function on our controller. Worry not, it is simply a matter of cutting and pasting and giving the new function a name.

Great, now we need to update our view to call the above function, so our whole pagination markup now looks like this:

The only thing left to do now disabling the next button when we have reached the end of our data set, this is your assignment! What? OK, OK, I’m sorry! I’ll give you something harder next time 😀

Hint: Disable next button when your needed number of pages matches your current active page, in effect, when you’ve reached the last page!

I hope this has helped, if you can think of ways to improve on this, please leave a comment below, discussions are often more helpful than massive blog posts.