infinite scroll with ember.js (lazy loading)
I have a view where there can be a large number of items for the user to scroll through and I'd like to implement infinite scrolling to enable progressive loading of the content.
It looks like some folks have done pagination but Google doesn't bring up anyone discussing how they've done infinite lists with Ember/Ember Data. Anyone already worked through this and have a blog post/example code to share?
I've implemented an infinite scroll mechanism at the GitHub Dashboard
project, I'm currently developing. The feature is added in commit 68d1728.
The basic idea is to have a LoadMoreView
which invokes the loadMore
method on the controller every time the view is visible on the current viewport. I'm using the jQuery plugin inview for this. It allows you to register for an inview
event, which is fired when the element of the specified selector is visible on screen and when it disappears.
The controller also has properties which indicate whether there are more items to load and if there are currently items fetched. These properties are called canLoadMore
and isLoading
.
The LoadMoreView
basically looks like this:
App.LoadMoreView = Ember.View.extend({
templateName: 'loadMore',
didInsertElement: function() {
var view = this;
this.$().bind('inview', function(event, isInView, visiblePartX, visiblePartY) {
if (isInView) Ember.tryInvoke(view.get('controller'), 'loadMore');
});
}
});
where the loadMore
template is defined as follows:
{{#if isLoading}}
fetching some more stuff <img width="10" src="img/ajax-loader.gif" >
{{else}}
{{#if canLoadMore}}
<a {{action "loadMore" target="controller" }}>click to load more items</a>
{{else}}
<i>no more items</i>
{{/if}}
{{/if}}
The controller which handles the fetching of more items is then implemented as follows. Note that in the loadMore
method a query on the store is performed, which loads a specific page of of entries for a model.
App.EventsController = Ember.ArrayController.extend({
currentPage: 1,
canLoadMore: function() {
// can we load more entries? In this example only 10 pages are possible to fetch ...
return this.get('currentPage') < 10;
}.property('currentPage'),
loadMore: function() {
if (this.get('canLoadMore')) {
this.set('isLoading', true);
var page = this.incrementProperty('currentPage');
// findQuery triggers somehing like /events?page=6 and this
// will load more models of type App.Event into the store
this.get('store').findQuery(App.Event, { page: page });
} else {
this.set('isLoading', false);
}
}
});
The only thing left is to initially set the content
of the controller to the result of a filter
function, so the content
is updated when new models are loaded into the store (which happens due to the findQuery
method in the loadMore
of the controller). Also, a query
hash is added when the filter
is invoked. This ensures that an initial query to the server is made.
App.eventsController = App.EventsController.create({
content: []
});
var events = App.store.filter(App.Event, { page: 1 }, function(data) {
// show all events; return false if a specific model - for example a specific
// type of event - shall not be included
return true;
});
Were you aware of the newly released Ember.ListView component?
https://github.com/emberjs/list-view
It was announced at the February San Francisco Ember Meetup. Here's a slidedeck from Erik Bryn, one of the Ember Core developers about using it:
http://talks.erikbryn.com/ember-list-view/
I'm writing an infinite pagination plugin for Ember based on @pangratz's work.
Please fire any issues on there if you have questions or improvements that you'd like.
I would recommend using Ember Infinity addon. It supports Ember 1.10 through to 2.0+. It's relatively easy to setup. You only need to modify your route and template.
Route (Product
is example model):
import InfinityRoute from 'ember-infinity/mixins/route';
export default Ember.Route.extend(InfinityRoute, {
model() {
/* Load pages of the Product Model, starting from page 1, in groups of 12. */
return this.infinityModel('product', { perPage: 12, startingPage: 1 });
}
});
Template:
{{#each model as |product|}}
...
{{/each}}
{{infinity-loader infinityModel=model}}
When {{infinity-loader}}
component becomes visible it sends an action to your route, so it knows to update model array with new (fetched) records.
First request will be sent to:
/products?per_page=12&page=1
So you also need to prepare your backend API to handle these query params. It's obviously customizable, take a look at Advanced Usage section of Readme.
Note:
Both using ListView
(@commadelimited's answer) and views with ArrayController
(@pangratz's answer) is deprecated/removed as of Ember 2.0 being stable version.