Question

I used that "counts-by-room" example from Meteor's docs to count the number of "reviews" for an "entry" on my project. Now, when I click through to an entry, the template isn't loading. It requires a refresh. Did I mess the example up somewhere? Here's my code. I don't really understand the "initializing" part of this, and don't know what to play with to get it to work correctly.

Server:

Meteor.publish("counts-by-entry", function (entryId) {
  var self = this;
  check(entryId, String);
  var count = 0;
  var initializing = true;
  var handle = Reviews.find({entry: entryId}).observeChanges({
    added: function (id) {
      count++;
      if (!initializing)
        self.changed("counts", entryId, {count: count});
    },
    removed: function (id) {
      count--;
      self.changed("counts", entryId, {count: count});
    }
  });
  initializing = false;
  self.added("counts", entryId, {count: count});
  self.ready();
  self.onStop(function () {
    handle.stop();
  });
});

Client (iron-router):

  this.route('entry', {
      path: '/entry/:_id',
      layoutTemplate: 'layout',
      yieldTemplates: {
          'navigation': {to: 'navigation'},
          'pt_entry': {to: 'content'}
      },
      waitOn: function () {
          Meteor.subscribe('singleEntry', this.params._id),
          Meteor.subscribe('entryReviews', this.params._id),
          Meteor.subscribe('counts-by-entry', this.params._id);
      },
      data: {
          singleEntry: function () {return Entries.findOne()},
          reviews: function () {return Reviews.find({entry: Session.get('entryId')}, {sort: {date: -1}})},
          count: function () {return Counts.findOne(Session.get("entryId")).count + " reviews."}
      },
      before: function() {
          Session.set("activeNav", Router.current().route.name),
          Session.set("entryId", this.params._id);
      },
      notFoundTemplate: 'notFoundTemplate'
  });

and also:

Counts = new Meteor.Collection("counts");
Was it helpful?

Solution

I just tried recreating the minimum amount required of your example to get it to work and I don't have the same issue as you.

Do you have the javascript console open in your browser? I would look for an error there, usually this kind of stuff happens when a helper is called upon when the collection data isn't available to the client. This is what iron-router's waitOn fixes which you have made use of.

In my reproduction I only have the one subscription (counts-by-entry) so maybe there is a issue with the other ones.

As for what the initializing part does:

The publish block is a piece of code that will be run for each client subscription. It does 2 things, it provides the client with the initial payload of data which in a traditional publication would be all the documents from a collection query then it reacts to changes that affects the result of the query and sends just those changes to the client.

Here is the most common publication you will see:

Meteor.publish("all-reviews", function() {
 return Reviews.find({});
});

Meteor hides the complexities of what is really going on in this publication. This is closer to what is really going on:

Meteor.publish("all-reviews", function() {
var self = this;

//this is the query we want realtime updates for
//when relevant additions, removals or changes happen the correct callbacks will fire...
var handle = Reviews.find({}).observeChanges({
  added: function(id, fields) {
    //when a document is added it gets sent to the client.
    //Note: the initial payload of data happens here, lets say you had 5 reviews
    //this would get called 5 times as soon as a user subscribes.
    self.added("reviews", id, fields);
  },
  removed: function(id) {
    //when a document is removed the client is told which one
    self.removed("reviews", id);
  },
  changed: function(id, fields) {
    //when a document has a change made to its fields the changes get sent
    self.changed("reviews", id, fields);
  }
});

//letting the client know that the initial payload of data has been sent.
//Stuff like iron-routers waitOn would rely on this message before loading a template
//that relies on this publication
self.ready();

//stops the publication running forever. This will fire when a client no longer needs a 
//subscription or when they close the page.
self.onStop(function() {
  handle.stop();
});
});

As for what it going on in the docs example with the initializing flag. The initializing flag is used as a way of simply counting all the initial payload of existing reviews in your case then after the observeChanges call telling the client how many there are. This is an optimisation on the other way of doing it which would be to send the client several self.changed messages during the initial payload.

Maybe it will make more sense if I show how it would be done without the flag:

Meteor.publish("counts-by-entry", function (entryId) {
  var self = this;
  check(entryId, String);
  var count = 0;
  //we need to initially add the document we are going to increment
  self.added("counts", entryId, {count: 0});
  var handle = Reviews.find({entry: entryId}).observeChanges({
    added: function (id) {
      count++;
      //So for example if there were 100 reviews for this entry when the user 
      //subscribes this self.changed would get called 100 times:
      //self.changed("counts", entryId, {count: 1})
      //self.changed("counts", entryId, {count: 2})
      //self.changed("counts", entryId, {count: 3})
      //self.changed("counts", entryId, {count: 4}) etc...
      //which is a waste when we can just initially tell the client how
      //many reviews there are at the point of them subscribing
      self.changed("counts", entryId, {count: count});
    },
    removed: function (id) {
      count--;
      self.changed("counts", entryId, {count: count});
    }
  });
  //remove the self.added(...) which handles the optimiation as explained above
  self.ready();
  self.onStop(function () {
    handle.stop();
  });
});

Regardless it doesn't look like that particular publish is the problem. I would expect the console to make it clear what the issue is

OTHER TIPS

waitOn should return an array with the subscription handles.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top