Overlapping Meteor publications

197 views Asked by At

I have a meteor app which has 2 publications for posts. One for all posts and one for featured posts. There are 2 featured posts - "Post 1" and "Post 4". I show featured posts on all pages, while i paginate all posts (including the featured posts) sorted by name. When i travel between pages, the data from the 2 publications get mixed up and show incorrect results.

Here is the code:

Meteor.publish('posts', function(page) {
  const skip = parseInt(page && page !== '' ? page : 0) * 3
  return Posts.find({}, {
    limit: 3,
    skip,
    sort: {
      name: 1
    }
  });
});

Meteor.publish('featured', function() {
  return Posts.find({
    featured: true
  }, {
    sort: {
      name: 1
    }
  });
});

On the client I am subscribing to both and displaying the data in 2 loops

Template.hello.onCreated(function helloOnCreated() {
  const instance = this;
  instance.autorun(function() {
    instance.subscribe('posts', FlowRouter.getParam('page'))
    instance.subscribe('featured')
  });
});

Template.hello.helpers({
  posts() {
    return Posts.find({}, {
      limit: 3,
      sort: {
        name: 1
      }
    })
  },
  featured_posts() {
    return Posts.find({
      featured: true
    }, {
      sort: {
        name: 1
      }
    });
  }
});

HTML template is as follows:

<template name="hello">
  <h2>Featured</h2>
  {{#each featured_posts}}
    {{> post}}
  {{/each}}
  <h2>Posts</h2>
  {{#each posts}}
    {{> post}}
  {{/each}}
</template>

The problem is the data from the 2 subscriptions are getting mixed up in the display.

On page 1 it shows it correctly:

Page 1

Featured
  post 1
  post 4

All Posts
  post 1
  post 2
  post 3

but when I go to page 2

Page 2

Featured
  post 1
  post 4

All Posts  -- Should be
  post 1        post 4
  post 4        post 5
  post 5        post 6

It shows "post 1" inside the "posts" which is featured but shouldn't be on page 2. When I go to page 3, i see "post 1" and "post 4" but they should not be there.

I understand how the publications and subscriptions work and why this is happening - because the publications merges the dataset. I am wondering if there is a work around to keep them separate?

2

There are 2 answers

0
ghybs On BEST ANSWER

If I understand correctly, your pages correspond to pagination of your "All Posts" list. The "page" number is sent as your subscription parameter, so that you receive a short list of your posts.

The difficulty here is indeed that your Client collection does not have all your documents in hand (since you limit them in your 'posts' publication), so you cannot use a similar skip logic as in the publication.

As proposed in Meteor Guide > Paginating subscriptions, you could use the percolate:find-from-publication Atmosphere package to easily retrieve the documents that come from your 'posts' publication, and only them.

// Server
FindFromPublication.publish('posts', function(page) {
  // Same logic
  const skip = parseInt(page && page !== '' ? page : 0) * 3
  return Posts.find({}, {
    limit: 3,
    skip,
    sort: {
      name: 1
    }
  });
});

// Client (no change in subscription)
Template.hello.helpers({
  posts() {
      return Posts.findFromPublication('posts', {}, {
        sort: {
          name: 1
        }
      });
    } // (no change in featured_posts)
});
2
AudioBubble On

I'd recommend using observe or observeChanges so that the data being published can be isolated on the client. The docs can be intimidating but it's actually easier than it looks.

It's just a matter of creating a special client-side collection and then using the name of that collection in the observe callbacks. Notice that the client-side collection name, "featured_posts", is used in the calls to self.added, self.changed and self.removed in the publication:

// client-side
const FeaturedPosts = new Mongo.Collection('featured_posts', {
  defineMutationMethods: false
});

// server-side
Meteor.publish('featured', function() {
    const self = this;

    const handle = collection.find(selector, opts).observe({
      added: function(doc) {
        self.added('featured_posts', doc._id, doc);
      },

      changed: function(newDoc, oldDoc) {
        self.changed('featured_posts', newDoc._id, newDoc);
      },

      removed: function(doc) {
        self.removed('featured_posts', doc._id);
      },
    });

    self.ready();

    self.onStop(function(err) {
      if (!err) {
        handle.stop();
      }
    });
  });
};