My current implementation looks pretty good so far. I am trying to make it look as native as possible—the ultimate goal is to make the user totally unaware of the technical solution. One of the vital features is replacing removed items with empty space so that we do not mess with the native scroll position and make it look for the user like there is actual content beyond the visible portion of the data set.

There are basically two types of an infinite scroller—a widget that has its own scroll position and an in-page content that scrolls with the page. These two can be present in one document at the same time. Facebook is a good example of this—there is the page content and the fixedly positioned ticker on the side. The only difference between these two is just the element that is considered to be the viewport. In case of the in-page variation, the viewport is the actual inner browser window whereas the widget one works within an element inside the page.

List Scroller Diagram

The idea is that we start with an empty container into which we insert the first chunks of the data set to fill it up. There is a configurable number or items to request from the data store at once. Then, when the user scrolls down and reaches the end of the buffer, more items are requested. A loading spinner is optionally displayed at the bottom.

As the user scrolls down the page, items above the viewport are removed from the DOM and moved to a pool of unused items. When the request to the data store is resolved and new data is received, the component first looks if there are any reusable elements and either replaces the view model data with the new items or creates a whole new element. These elements are then added to the end of the item container.

The user can scroll back in the other direction (upwards) and the exact same thing happens in reverse. Elements that are not visible at the end are removed and placed in the pool. More items are requested from the data store before the first visible item and the removed elements are reused when we receive the data.

One might argue that removing items immediately when they are out of the viewport is not a good idea as the need for requesting them from the data store again even if the user scrolls just a few items in the opposite direction is not a good idea from the user experience point of view. I feel like requesting the items again is the correct thing to do as the app-specific data store object can decide what to cache and there is no delay if the items are just taken out of the cache of the data store.

Object Model

One of the vital features of the scroller is supporting multiple item element templates so that there can be items of different types in the “stream”. Facebook, for instance, would like to have a text-only item, item with a photo, item with a link and so on.

To finally move over to some actual code, here is the minimal HTML structure required for a scroller:

<div ng-scroller>

<div ng-scroller-repeat=”post in posts”>

<div>{{post.author}}: {{post.text}}</div>

</div>

</div>

The ng-scroller element is considered to be the viewport element, the ng-scroller-repeat element is the item container (blue in the scroller diagram above) and the innermost element is an item template that is going to be duplicated and linked with a child scope created for each item.

The posts scope key in this case has to point to a data store object that implements the following interface for querying for items. If the key points to an Array, for convenience, it gets internally wrapped by an object that implements the required interface.

IScrollerDataStore {

void getRangeAfter(prev_id, length, function(Error, Array));

void getRangeBefore(next_id, length, function(Error, Array));

}

If an app requires multiple templates, it should utilize the ng-if and ng-switch directives as much as possible. At one point, I had a dedicated multiple template logic implemented in the scroller but in most cases, the existing means of achieving this are sufficient enough.