Pagination of subscriptions

There are several solutions out there for subscription paging, but I never really looked into any of them because some time ago I had devised my own solution. Over the years I became more and more disenchanted with my approach, because it seemed overly complex

Originally I did something like this

Publication = new Mongo.Collection('publication');
Meteor.publish('publication', function(query, skip){
  const self = this;
  const maxCount = Publication.find(query).count();
  const handler = Publication.find(query, {skip: skip, limit: 8}).observeChanges({
    added: function(id, object) {
      object._subscriptionId = self._subscriptionId;
      object.maxCount = maxCount;
      self.added('publication', id, object);
    },
    changed: function(id, fields) {
      self.changed('publication', id, fields);
    },
    removed: function(id) {
      // console.log('business.removed',id);
      self.removed('publication', id);
    }
  });
  self.ready();
  self.onStop(function() {
    if (handler) {
      handler.stop();
    }
  });
});

on the client side I would then grab one of the objects in a callback of the subscription to setup the pagination

subscribe('publication', query, skip, function(){
 const object = Publication.findOne();
 // feed object.maxCount to the pagination
});

This approach also had the advantage that I was able to tell my subscriptions apart on the client by filtering for _subscriptionId.

Long story short I wanted to simplify my approach, and also get away from observeChanges for every subscription to Publication , so I came up with the following, which works surprisingly well

Publication = new Mongo.Collection('publication');
PublicationCount = new Mongo.Collection('publication_count');
Meteor.publish('publication', function(query, skip){
  const self = this;
  // get the max count for the query without limit
  const maxCount = Publication.find(query).count();
  // see if there is an entry in PublicationCount for this connection id
  const pubcount = PublicationCount.findOne({connectionId: self.connection.id});
  var pubcountId = null;
  if(pubcount) {
    // update if exists
    PublicationCount.update(pubcount._id, {$set:{maxCount: maxCount}});
    pubcountId = pubcount._id;
  } else {
    // create if it did not
    pubcountId = PublicationCount.insert({connectionId: self.connection.id, maxCount: maxCount});
  }
  //
  // return array of cursors
  //
  return [
    Publication.find(query,{skip: skip, limit: 8}),
    PublicationCount.find(pubcountId),
  ];
});
//
// use Meteor.onConnection to clean up PublicationCount for disconnected connections
//
Meteor.onConnection(function(connection) {
  const connectionId = connection.id;
  connection.onClose(function(){
    let c = PublicationCount.remove({connectionId: connectionId});
    console.log(`onClose ${connectionId} removed ${c}`);
  });
});
//
// clean out PublicationCount when server starts
//
Meteor.startup(function(){
  PublicationCount.remove({});
});

On the client side I simply subscribe and read the count for the pagination

subscribe('publication', query, skip, function(){
 const count = PublicationCount.findOne();
 if(count) {
  // set up pagination with count.maxCount
 }
});

I would be interested what you think about this approach.

2 Likes

Will the count be updated when new document inserted or removed?

Yes, because whenever the subscription changes, the cursor for the counter will be also updated