Template subscriptions and autorun


#1

To set the context, this is the use of Template subscriptions together with Template autorun in the onRendered function.

The official documentation suggests this form:

Template.something.onRendered(function() {
  var self = this;
  self.autorun(function() {
    self.subscribe("name", function() {
      var thing = Collection.findOne(); // some reactive operation
      // do fun stuff with thing
  });
});

Which unsubscribes/resubscribes to the name publication reactively.

And that works very nicely. However I have used this form:

Template.something.onRendered(function() {
  var self = this;
  self.subscribe("name", function() {
    self.autorun(function() {
      var thing = Collection.findOne(); // some reactive operation
      // do fun stuff with thing
  });
});

Which also works very nicely - and feels more “right” to me. Subscribe only once and let reactivity do its thing.

So my question is which form is right and why?


Order of startup, subscribe, onCreate on a page reload
#2

I think you would use the first snippet over the second when you have reactive arguments that you pass into the subscription call. Otherwise I think it makes no difference.


#3

That does make sense, but when you say “no difference”, surely there is more overhead in unsubscribing and resubscribing - or is that ignored because the subscription’s not actually changed?


#4

I would say that you should prefer the 2nd form if you don’t have reactive arguments to the subscription, whether or not Meteor figures out on its own if it actually needs to un-/resubscribe instead of just staying suscribed. Whether it does already figure that out I don’t know.
The reason for preferring the 2nd form is (obviously?) that stuff that doesn’t need to rerun shouldn’t have to, i.e. proper scoping / structure of code that aligns with what behavior is actually desired. EDIT: I suppose that’s why the 2nd form feels better to you as well.


#5

I think Meteor is smart enough to not resubscribe when the subscription has not changed.

It’s in the docs under http://docs.meteor.com/#/full/meteor_subscribe:

If you call Meteor.subscribe within a reactive computation, for example using Tracker.autorun, the subscription will automatically be cancelled when the computation is invalidated or stopped; it’s not necessary to call stop on subscriptions made from inside autorun. However, if the next iteration of your run function subscribes to the same record set (same name and parameters), Meteor is smart enough to skip a wasteful unsubscribe/resubscribe.


#6

In the first code snippet, I think the autorun is useless (nothing reactive inside).


#7

Regarding the second code snippet, I would format it like this:

Template.something.onRendered(function() {
  // Subscribe
  var subs = this.subscribe("name");

  // Do reactive stuff when subscribe is ready
  this.autorun(function() {
    if(! subs.ready())
      return;
    var thing = Collection.findOne();
    ...
  });
});

It looks clearer to me, and also scales well if there are more than one subscription:

Template.something.onRendered(function() {
  var self = this;

  // Subscribe
  self.subscribe("s1");
  self.subscribe("s2");

  // Do reactive stuff when subscribe is ready
  self.autorun(function() {
    if(! self.subscriptionsReady())
      return;
    var thing = Collection.findOne();
    ...
  });
});

#8

I’m currently using Iron Router for subscriptions, but I’m slowly migrating to Template subscriptions. This is how I’m doing it so far:

Template.template_name.onCreated(function() {
  var instance = this;

  instance.state = new ReactiveDict;

  instance.autorun(function() {
    var subscription1 = instance.subscribe("Collection_Name1");
    var subscription2 = instance.subscribe("Collection_Name2", Session.get('session_var'));

    if (subscription1.ready()) {
      var record = instance.document();

      // Reactive vars
      if (record) {
        instance.state.set('reactive_var', record.field);
      } else {
        instance.state.set('reactive_var', null);
      }
    }
  });

  instance.count = function() {
    return Collection_Name1.find({}).count();
  }

  instance.document = function() {
    return Collection_Name1.findOne({
      _personId: Meteor.userId()
    });
  }
});

Template.template_name.helpers({
  setFormMode: function() {
    var count = Template.instance().count();
    var data_set = Template.instance().document();

    if (count === 0) return {
      doc: null,
      type: "insert"
    };
    else if (data_set) return {
      doc: data_set,
      type: "update"
    };
  }
});

Will someone please review this for correctness? What would you do differently?

For example,

What’s so wrong with putting the subscription1 inside of autorun; will I still get updates to subscription1 data if this is outside autorun? How would you do it?

Why but the subscriptions in onRendered vs onCreated and vis-versa?

I use instance.document to access query the published cursor, and is used by the helper methods. What would you do?


#9

I haven’t tested this, but instead of if (subscription1.ready())), try if (instance.subscriptionsReady()). This will cover all the subscriptions your template is waiting for.


#10

Putting subscription1 inside an autorun is useless (it will even cause a (very minor) overhead). A subscription doesn’t need an autorun to update the data.
subscriptions2, on the other side, needs an autorun, because one of its arguments might change reactively.

You usually put subscriptions in onCreated, so that they are run as soon as possible and while the initial DOM is built. There is usually no need to wait for onRendered.

I wouldn’t do that. I think your collection access functions belongs with the collection, not with the template:


// File Collection_Name1.API.js
Collection_Name1.API = {
  count: function() {
    return Collection_Name1.find({}).count();
  },

  userDocument: function() {
    return Collection_Name1.findOne({ _personId: Meteor.userId() });
  }
};

// File template_name.js
Template.template_name.helpers({
  setFormMode: function() {
    var count = Collection_Name1.API.count();
    var data_set = Collection_Name1.API.userDocument();

    if (count === 0) return {
      doc: null,
      type: "insert"
    };
    else if (data_set) return {
      doc: data_set,
      type: "update"
    };
  }
});

#11

.API is a bit awkward, but I do like the general idea of this. I say “awkward” because we generally don’t put “what something is” into “the name we call it”, e.g. I say “my macbook”, not “my macbook laptop personal computer”, even though my MacBook is indeed a laptop / personal computer. 50% of programming (or maybe 33%) is finding really good names for things, or at least avoiding the absolutely terrible ones, and calling an API “something something API” is not mega terrible, but easy enough to improve – and very easy to spot, too.
I understand that you’ve probably just been using ANY name for that, but I’m just stating this because such things actually happen in real code, and it’s good to pay attention to it. AND, as I said, I really like this idea of extending the standard collection API with our own, “user-defined” methods, and I’m trying to think of a proper namespace to put them in for myself.
Maybe .ext could work, like Meteor.users.ext.findAdminUsers(), Products.ext.findNewAdditions(), Threads.ext.archiveOlderThan(30, 'days')


#12

@seeekr:
Agreed.
ext is not bad.
You can also find some inspiration here.


#13

Cool, thanks for linking that. Currently really getting into the depths of Meteor development and good to see thoughts on structure, organization etc from others who’re also spending lots of time working in and thinking about Meteor! (Are you part of percolate? I don’t really know most of the people here on the forums, begun participating only a week or so back!)
EDIT: I suppose this message was a bit off-topic. Feel free to respond in a PM if you like, that’s probably more appropriate :panda_face:


#14

If I don’t put subscription1 inside a autorun, the ‘reactive_var’ reactive variable will not get set because the ready() will not get called on this publication. Or another way, If I want ready() to get called and set the reactive var, I need it inside a autorun from what I found.


#15

You are talking about the ready(); I was talking about the subscribe().


#16

A reason why you might want your subscription inside the autorun is if it’s subscribing based on reactive parameters. A common example would be paginating a publication with a limit parameter.

In that case, you do want the subscription to re-run every time that parameter changes, so I’m pretty sure you would put it inside the autorun.


#17

Another thing, I’m letting my admins impersonate users.

I use Meteor.connection.setUserId(userId)

In order to get your subscriptions to reset after swapping users, some recommend placing the subscriptions inside of autorun.


#18

Subscriptions are automatically refreshed when user logs in/out. No need for an autorun. See here.


#19

+1 but @aadams do not use log in/out - so publish seems not to re-run. Or you can confirm that Meteor.connection.setUserId(userId) works like log in/out?


#20

Question. “// do fun stuff” does this happen in the onrender? Could fun stuff simply be “return thing”? I’m trying to reconcile…

currently I have a helper function, say like this:

thing : function() {
return Thing.find({});
}

then in my HTML I have an {{#each thing}} that iterates through that set. Does the helper function now need to live in the onRendered?