Infinite scroll with AngularJS and Rails

In BIME, we offer the ability to create unlimited dashboards, queries and connections. This is really interesting for our clients, and we started to observe accounts becoming bigger and bigger.

The Queries Library contains all the queries the user has access to. To be able to render the library smoothly, even with a ton of queries, we added a generic implementation of an infinite scroll.

The Infinite Scroll

The main purpose of the infinite scroll is to unload the browser if a collection of items is huge. For example, if you have thousands of products in a reseller catalog, the browser will only display the first few. When the user scrolls down to the bottom of the page, the next items are loaded and added to the page infinitely (until the end of the collection). This technique is also an alternative to the pagination mechanism.

AngularJS directive for the Infinite scroll

To handle the infinite scroll, we need a container which contains the items that will be displayed. A sample div is enough. For example:

<div class="container" bime-infinite-scroll="loadNewItems()">
  <div ng-repeat="item in items">
    {{ item.nam }}
  </div>
</div>

We added a custom directive bime-infinite-scroll which triggers the load of the next items when necessary by calling the corresponding method of the AngularJS controller.

Let’s have a look at this directive (Note that the code is written in TypeScript):

 angular.module('bime').directive('bimeInfiniteScroll', [
  () => {
      return {
          restrict: 'A',
          link: ($scope, element: JQuery, attr: any) => {
              var raw = element[0];
              element.bind('scroll', <any> _.throttle(() => {
                  if (raw.scrollTop + raw.offsetHeight >= raw.scrollHeight) {
                        $scope.$apply(attrs.bimeInfiniteScroll);
                  }
              }, 200));
          }
      };
  }
 ]);

We have to listen for a DOM event which is out of the Angular scope and digest. So, we listen to the scroll event of the element on which the directive is applied (the container in our case). When the scroll down hits the bottom of the page we call the function passed as an attribute value of the directive.

At this stage, we automatically call a function of the current scope when the user scrolls down and hits the bottom of the page. Let’s have a look at how we load the next items.

 $scope.page = 1;
 $scope.items = [];

 $scope.loadNewItems = () => {
      itemsFactory.getAll({page: $scope.page}).then((items: Item[]) => {
          $scope.items = $scope.items.concat(items);
          $scope.page += 1;
      });
  };

We have the page variable which handles the number of pages we have already loaded, items which contains all the items displayed, and the function called by the infinite scroll directive (loadNewItems).

The itemsFactory refers to an injected service which handles the call to the backend on the correct resource route. We heavily use restangular to manage the calls to the backend. For those of you who don’t know this library, it is an abstraction of the REST principle for AngularJS and integrates very well with a REST-based backend like Rails.

So, when this function is called, a HTTP GET call is made to the server with the current page number. When the view gets the result, we add it to the list of already loaded items and increment the page number. This way, each time the user gets to the bottom of the page, the new elements are added to the DOM. After the items variable has been updated, the double data-binding of Angular takes place and the DOM is rendered with the updated object.

Rails controller for the Infinite scroll

So far, we have implemented the view part of the Infinite scroll. However, we need a technique on the server to pull new objects based on the page number passed as a parameter. Fortunately, as we use Rails, the ActiveRecord interface contains methods that fit our needs for this specific feature. Let’s have a look at the controller handling this call:

 class ItemsController < ApplicationController

   def index
     @items = current_user.items

     if params[:page]
       @items = @items.page(params[:page]).per(50)
     end
   end

 end

What is interesting here is how easy it is to paginate a collection with ActiveRecord. The page method indicates the starting page number and the per method specifies the number of items to retrieve.

Following this controller, a JSON view renders this collection to the view, and these new items will be added to the page.

Conclusion

Again, using a bunch of modern technologies, we can see how easy it is to get a fast implementation of an infinite scroll mechanism. This way we load only a few elements in the view even if the collection contains a ton of data. By bringing new data as the user requests them, we ensure fast performance of the application in the browser without compromising the user experience.

← Back to Home

Yannick Chaze
comments powered by Disqus