Template subscriptions and autorun

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.

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.

2 Likes

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

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();
    ...
  });
});
5 Likes

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?

1 Like

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.

1 Like

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"
    };
  }
});
1 Like

.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')

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

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:

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.

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

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.

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.

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

1 Like

+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?

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?

Not sure I understand quite what you’re asking. Helpers are defined in a Template.xxx.helpers() block - so not within the context of a Template.xxx.onRendered()

Hi,

I really wonder… I just use the code from this thread, but my subscription to apps is not being re-run. I would like to show only the apps with an ID that is present in the GeneratedResources collection…

Template.generation.onCreated(function() {
    var self = this;
    self.autorun(function(){       
        self.subscribe('generatedResources', function() {
            console.log('generatedResources changed, so update the apps subscription');
            const generatedAppsFromUser = GeneratedResources.find()
            .map(function(resource) {
                return resource.appId;
            });
            console.log('onCreated generatedResources are: ', generatedAppsFromUser);
            //now get all the apps from Qlik Sense, but filter them so that only the apps are show which the current user has generated
            Meteor.subscribe('apps', generatedAppsFromUser);            
        });
    })
})

My publication

Meteor.publish('apps', function(generatedAppsFromUser) {
    if (Roles.userIsInRole(this.userId, ['admin'], Roles.GLOBAL_GROUP)) {
        return Apps.find();

    } else {
        console.log('Client subscribed to collection, with these generated app ids: ', generatedAppsFromUser);
        if (!generatedAppsFromUser) {
            generatedAppsFromUser = [];
            console.log('##### No generated resources exists yet, so only show the template apps')
        } else{
            console.log('### publication recevied these generated app ids for the user: ', generatedAppsFromUser);
        }
        return Apps.find({
            $or: [{ "id": { "$in": generatedAppsFromUser } }, { "stream.name": "Templates" }, { "stream.name": "Everyone" }]
        });
    }
    this.ready();
});

Funny, if I use another tracker.autorun inside it works… But I am a bit lost now :slight_smile:

  • why is this happening?
  • what is the difference between tracker.autorun and this.autorun?
  • is the issue in my previous code, that I have put the subscription to apps in the callback function on the GeneratedResources collection on which it has a dependency?

thank you

Template.generation.onCreated(function() {
    this.subscribe('streams');
    this.subscribe('customers');

    var self = this;
    self.autorun(function() {
        self.subscribe('generatedResources', function() {
            console.log('generatedResources changed, so update the apps subscription');
            Tracker.autorun(function() {
                const generatedAppsFromUser = GeneratedResources.find()
                    .map(function(resource) {
                        return resource.appId;
                    });
                console.log('onCreated generatedResources are: ', generatedAppsFromUser);
                //now get all the apps from Qlik Sense, but filter them so that only the apps are show which the current user has generated
                Meteor.subscribe('apps', generatedAppsFromUser);
            })

        });
    })
})

Source: http://stackoverflow.com/questions/29443513/meteor-tracker-autorun-observechanges-collections-not-working-as-expected