Ze Jibe
Ze Jibe

Reputation: 1043

How to deal with dynamic subscriptions in Meteor?

I have a publication whose scope depends on a element property from another collection. Basically it looks like this on the server:

Meteor.publish('kids', function (parent) {
    return Kids.find({ _id: { $in: parent.childrenIds } });
}

In the example above parent.childrenIds is an array containing the _id's of all the kids that are children of the parent. This works fine until I want to add a new child to the parent:

newKidId = Kids.insert({ name: 'Joe' });
Parents.update({ _id: parentId }, { $push: { childrenIds: newKidId } });

This works on the server for the Kids collection (i.e., the new kid is added) and it updates the parent's childrenIds array with the newKidId. BUT it does not update the above 'kids' publication (the cursor is not updated/modified). As a result, the client's Kids collection is not updated (and it looks like the change to Kids is rolled back on the client).

When the client refreshes, all publications are stopped/restarted and the new kid (Joe) is finally published to the client.

Is there a way to avoid refreshing the client and forcing the re-publication of the Kids collection (ideally only sending the new kid Joe to the client)?

Upvotes: 2

Views: 2527

Answers (4)

Mitar
Mitar

Reputation: 7070

These days you can simply use reactive-publish package (I am one of authors):

Meteor.publish('kids', function (parentId) {
    this.autorun(function (computation) {
        var parent = Parents.findOne(parentId, {fields: {childrenIds: 1}});
        return Kids.find({_id: {$in: parent.childrenIds}});
    });
}

It is important to limit Parents' query fields only to childrenIds so that autorun does not rerun for any other changes to Parents document.

Upvotes: 0

Mitar
Mitar

Reputation: 7070

In meantime there were quite some packages made to deal with reactive publish functions. I am an author of meteor-related, and at the end of the package's README I compare my package with few other packages:

Upvotes: 1

user728291
user728291

Reputation: 4138

I think you need to use observe in the publish function if a published query relies on a second query. Deps.autorun on the client is not necessary.

See discussions on Meteor server reactivity and Reactive updates when query is filtered by another query.

This is some code based on http://docs.meteor.com 'counts-by-room' example.

Meteor.publish( "kids", function(parent_id){
  var self = this;

  Parents.find({_id: parent_id}, { childrenIds: 1 }).observe({
    added: function (document){
    document.childrenIds.forEach( function(kidId){
      self.added("kids", kidId, Kids.findOne( { _id: kidId}, {name: 1, _id: 1} ));
      });
    },

    removed: function (oldDocument){
      oldDocument.childrenIds.forEach( function(kidId){
        self.removed("kids", kidId, Kids.findOne( { _id: kidId}, {name: 1, _id: 1} ));
        });
    },

    changed: function (newDocument, oldDocument){
      var oldLength = oldDocument.childrenIds.length;
      var newLength = newDocument.childrenIds.length;
        if (newLength > oldLength){
          self.added("kids", 
                      newDocument.childrenIds[newLength-1], 
                      Kids.findOne( { _id: newDocument.childrenIds[newLength-1] }, {name:1, _id:1}) );
        }
        else{
          self.removed("kids", 
                        oldDocument.childrenIds[oldLength-1], 
                        Kids.findOne( { _id: oldDocument.childrenIds[oldLength-1] }, {name:1, _id:1}) );
        }
      }
   });

   self.ready();      
});

Upvotes: 0

jagill
jagill

Reputation: 692

One of the things that is often misunderstood in Meteor is that there is no reactivity on the server. Dynamic descriptions need to be handled by Deps.autorun blocks on the client. To do this, first make sure you are not including the autopublish package by using this command in project directory:

$ meteor remove autopublish

Second, set up an autorun block on the client like:

Meteor.startup(function(){
  Meteor.subscribe('parents');

  Deps.autorun(function() {
    parent = Parents.findOne();
    if (!parent) return;
    Meteor.subscribe('kids', parent);
  });
});

This will tear down and set up subscriptions as the parent object changes.

You can see a full working example at https://gist.github.com/jagill/5473599 .

Upvotes: 7

Related Questions