Pagination with Laravel 4 and Angular JS

15 Aug 2013

Pagination is a common programming problem to solve - nearly all projects we work on we’ll need pagination in one form or another. As a result, it’s something that is good to get right early on so that we can re-use our code over and over again and not spend anytime reinventing the wheel each time it comes up.

In order to follow through this article, I’ll assume you have at least some knowledge in the following:

  • Laravel 4
  • Angular JS

It goes without saying that should know a thing or two about javascript as well ;)

As part of every one-page application, our views and view logic should probably be apart of Angular JS, so I’ll be using that assumption in this article. We’ll be touching base on a number of concepts from both frameworks:

  • Laravel 4 routing
  • Angular JS resources
  • Angular JS services

Because most of the logic is going to be on the client-side (rendered and parsed within the browser), that frees up a lot of time for our server to be doing things it might otherwise be doing. This means our server-side implementation will be much simpler than the traditional method, and so we’ll get the structures we need in place first, on Laravel 4.

Setting up the route

In this example we’ll pretend that we want to get a number of posts - so we’ll setup a route for that request. I’ll assume here that you already have a Posts model setup.

1 // routes.php
2 Route::get( 'posts', function() {
3   return Post::paginate( $limit = 10 );
4 });

In order to be able to return a Paginator object as it does in this article, please see: Making the Paginator API-friendly in Laravel 4.

Here all we’re doing is telling Laravel that all requests to /posts will be served by our anonymous function which is just going to return a list of posts, already paginated. What’s that going to do? Well, it should return a JSON object that looks something like this:

1 {
2   last: "9",
3   page: "1",
4   per_page: "10",
5   results: [ ... ],
6   total: "85"
7 }

This is a JSON object returned by our server that gives us some information such as:

  • The last page (in this case 9)
  • Which page we’re currently on
  • How many items we’re returning per page
  • The array of results for this current page
  • The total number of records

For those of you who have worked with Laravel’s pagination library before - this probably looks pretty familar. That’s because Pagination in Laravel simply returns an array with the relevant properties. The trick is making this work and gluing it all together on the front-end.

Creating the resource service

To access the server, we’re going to setup a Post resource that we’ll call from within our angular controller to make the request to our server for the paginated data. Remember to do this you need to have the ng-resource dependency available for your application.

1 var app = angular.module( 'paginator-application-example', [ 'ngResource' ] );
2 
3 app.service( 'Post', { '$resource', function( $resource ) {
4   return $resource( 'posts/:id', { id: '@id'} );
5 }});

This creates a Post service that we can inject into our controllers (or directives) to make requests to the server for post records. Now we can use this in our controller and setup our pagination!

If you like to use ng-resource, see my article: using ng-resource in a more RESTful manner.

Setting up the controller for pagination

The most complex part of this whole task is creating the controller and having it set the appropriate scope variables for pagination in our view. What we’re going to do however, to keep our controller nice and clean - is setup a second service called Paginator, which will inject a new pagination object into our controller for this functionality. This means that our controller only needs to tell the paginator what service to use for the querying, as well as pass some data to our view to manage the navigation. Let’s build it!

1 app.controller( 'posts.controller', [ '$scope', 'Post', function( $scope, Post ) {
2   $scope.posts = Post.query();
3 }]);

This is all very simple - and pretty much textbook Angular. What we’re doing is making a call to our /posts/ route on our server via the Post service, and assigning a posts variable to $scope so that we can work with it in our pagination directive, which we’re now going to build. There’s a fair bit of code in the next method, so read it carefully and I’ll go through it with you in a moment.

 1 app.directive( 'pagination', [ function() {
 2   return {
 3     scope: { results: '&' },
 4     link: function( scope ) {
 5       var paginate = function( results ) {
 6         if ( !scope.currentPage ) scope.currentPage = 0;
 7 
 8         scope.total = results.total;
 9         scope.totalPages = results.last;
10         scope.pages = [];
11         
12         for ( var i = 1; i <= scope.totalPages; i++ ) {
13           scope.pages.push( i ); 
14         }
15         
16         scope.nextPage = function() {
17           if ( scope.currentPage < scope.totalPages ) {
18             scope.currentPage++;
19           }
20         };
21         
22         scope.prevPage = function() {
23           if ( scope.currentPage > 1 ) {
24             scope.currentPage--;
25           }
26         };
27         
28         scope.firstPage = function() {
29           scope.currentPage = 1;
30         };
31         
32         scope.lastPage = function() {
33           scope.currentPage = scope.totalPages;
34         };
35         
36         scope.setPage = function(page) {
37           scope.currentPage = page;
38         };
39       };
40 
41       var pageChange = function( newPage, lastPage ) {
42         if ( newPage != lastPage ) {
43           scope.$emit( 'page.changed', newPage );
44         }
45       };
46 
47       scope.$watch( 'results', paginate );
48       scope.$watch( 'currentPage', pageChange );
49     }
50   }
51 }]);

There’s a lot going on here, so let’s break it down step-by-step. First we’re defining our directive, called “pagination” on our module. We’re then defining it by creating a link function, but also restricting the scope to what we pass in through the view. Let’s look at that.

1 scope: { results: '&pagination' },

What this will do, is create a two-way data binding to an attribute on our directive called “pagination” and store it on a scope property called “results”. Why is this important? It ensures that our directive acts completely on its own with no outside influence (such as $scope pollution) - and we simply tell it what the resulting data set is from our server. I’ll cover this in a little more detail below when we get to the final step.

After this, we start working with our link function and we’re creating a new function, called paginate. What this method is doing is looking at the result set that has been passed into the directive and setting the required variables and properties that we’ll need in our view below. Properties such as the current page, the total number of records, how many pages there are - as well as a page array that we’ll use later to generate the paginated links. And finally, it creates a number of functions that we’re assigning to the view for usable functionality - aka, functions that the user themselves can execute for their desired results.

Next, you’ll see another function called pageChange. This is a very simple method that simply watches the the currentPage and emits an event upwards (so that our controllers can respond) with the new page that’s been requested.

Finally, you’ll see two $watch calls on scope that watch results, and currentPage properties - and pass it the required callbacks that will handle the functionality once those properties change.

This is all fine and well - but there’s one thing missing. We have not yet implemented this in our view, or even got any pagination HTML! Yikes! That’s what we’ll do now - we’ll create the HTML that our pagination directive will be responsible for, and provide all the functionality that our end-user will expect. Let’s update our pagination directive from before, by adding a new property to the directive definition called template that will handle this for us:

1 tempate: '<ul ng-show="totalPages > 1">' +
2          '  <li><a ng-click="firstPage()">&laquo;</a></li>' +
3          '  <li><a ng-click="prevPage()">&lsaquo;</a></li>' +
4          '  <li ng-repeat="n in pages">' +
5          '    <a ng-bind="n" ng-click="setPage(n)">1</a>' +
6          '  </li>' +
7          '  <li><a ng-click="nextPage()">&rsaquo;</a></li>' +
8          '  <li><a ng-click="lastPage()">&raquo;</a></li>' +
9          '</ul>',

You could also put this in a templateUrl property and have it loaded from the server, and even disable certain links based on the current page.

As you can see, and as you would expect - our pagination template has all the required functionality you would need from such a requirement. Next and previous links, first and last page links, as well as a full paginated page list. Feel free to add your required classes and style it as you need!

The final step, is to wire this up in our view, so let’s do it.

1 <div pagination="results"></div>

Remember how we stated earlier that we’d pass in the results to our pagination directive? Well that’s exactly what’s going on here - this HTML sits in the template that our controller manages, and we simply call the pagination directive and pass it the name of the variable that contains the returned results.

And there you have it - an isolated pagination directive that responds to data requests, as well as notifies our controllers when changes are requested!

Pagination is not exactly a simple piece of functionality, especially if you have to re-write it all the time. Hopefully this article has demonstrated that not only is it super easy (and fast) to write excellent, modular features in Angular - but has also proven that good practice is an easy objective to achieve. Comments, feedback and criticisms welcome as usual! :)

comments powered by Disqus