Possible bug in Meteor with Template.subscriptionsReady?


#1

Take this code from the official docs (slightly modified):

<template name="notifications">
  {{#if Template.subscriptionsReady}}
    <!-- This is displayed when all data is ready. -->
    <ul>
    {{#each notifications}}
      <li>{{message}}</li>
    {{/each}}
    </ul>
  {{else}}
    {{> spinner}}
  {{/if}}
</template>

Now I’ll add on my own code here:

Template.listing.onRendered(function () {
  var template = this;

  template.subscribe('notifications', function () {
    console.log(template.$('ul > li'));
  });
});

And on the server side:

Meteor.publish('notifications', function () {
  //intentionally sleep for 2s to simulate lag
  Meteor._sleepForMs(2000);
  return Notifications.find();
});

So when I load the page, I get a spinner for two seconds, and then console.log doesn’t return what I expect. In fact, even if I try to log template.$('ul'), I get nothing, which is strange. It seems that the subscribe callback is firing before Blaze gets a chance to draw the page out that’s contained within the {{#if Template.subscriptionsReady}} block.

Just so people can test this for themselves, I’ve put it up on Meteorpad: (be sure to open the output window in a new window to access the console)

http://meteorpad.com/pad/X2psJgD4mtaChfzNP/subscriptionBug


#2

Can it be that onRendered fires when the template renders, which is before the subscription is ready? Otherwise you wouldn’t see the spinner at all? In other words, I think this works exactly as advertised.

What I find odd is that meteorpad doesn’t show ANY of my console.log messages …


#3

I cloned your meteorpad and achieved exactly what you want by subscribing in onCreated of myThings, but then including another template to actually list the collection. Check it out


#4

What I find odd is that meteorpad doesn’t show ANY of my console.log messages

Remember, you have to click the icon to open the output in a separate window in order to see the console messages.

I’ll check out your clone. Thanks!


#5

I see this works, but I’m really confused as to why my version doesn’t work. It seems like it should.


#6

Well, your version works as it should, it just doesn’t work like you expect it to :wink:

What happens is that onRendered fires way before your subscription is ready, which it has to, otherwise you wouldn’t be able to display the else block of your Template.subscriptionsReady, right? So the DOM in onReady basically contains your spinner, and not the UL > LI yet


#7

For anyone interested:


#8

Shiver me timbers! That just saved me a lot of pain, matey) Yohoho!


#9

Example of using Tracker.afterFlush to execute Javascript code after all subscriptions and rendering is complete:
(copied from my GitHub link above)

Template.listing.onRendered(function () {
  var template = this;

  template.subscribe('listOfThings', function () {
    Tracker.afterFlush(function() {
      template.$('#thing').addClass('highlight');
    });
  });
});

#10

That’s awesome indeed. Thanks for pointing it out.

Just one thing, though. Using the onRendered() callback reruns the subscription whenever the template is rerendered. This cannot lead to performance issues?


#11

I’m not sure. What are you thinking might go wrong? People put subscribe calls within Tracker.autorun and template.autorun. I’m not sure how that’s any different.


#12

I’m not sure as well, just thinking out loud. The Tracker.autorun() will rerun whenever the subscription is changed (for instance, when the document is updated). The onRendered() will fire again more unpredictably. Quoting this blog post:

(1) Rendered callbacks probably run more often then you think they do. Rendered callbacks run whenever the template is rerendered, and whenever a subtemplate (a template contained within the template) is rendered. To reduce the number of times a rendered callback is run, make sure that your template doesn’t contain any sub-templates.

Again, I’m not saying this is bad. Actually I quite agree with you, just trying to certificate myself this is the best way to go.


#13

Not sure I agree with that blog post. I ran my own test, and when I rendered a template within a template, the main template’s onRendered callback did not fire.

In my experience, onRendered is only called once: when a template’s DOM is rendered. Of course, if you surf to a specific page repeatedly, onRendered is called every time you hit that route and it renders the template. But that’s normally when you’d want to subscribe to a publication anyway (for performance and security reasons).

*Assuming subtemplate means this:

<template name="main">
  {{> someOtherTemplate}}
</template>

<template name="someOtherTemplate">
  Content.
</template>

If the author means this though:

<template name="main">
  <template name="sub">
  </template>
</template>

Then that’s a different thing. And I don’t even know why someone would do that.


#14

onRendered is not called when the template is “rerendered”, but when the template is first rendered. There is no such thing as template “rerendering” in Meteor, only templates that are created, rendered once, partially updated (no call to onRendered here), then finally destroyed. Consequently:

  1. The onRendered callbacks of your main templates (where subscriptions are done) are usually not called so often.
  2. When the onRendered callbacks is called, it usually means you want to subscribe to new data anyway.

#15

Well, given that the callback is called just once, makes total sense though. Maybe because of that blog post, I had the impression the onRendered callback wasn’t reliable enough. Thanks anyway.


#16

Rendered callbacks probably run more often then you think they do.

I’d be curious to know what version of Meteor this blog post is referring too. Pre-Meteor 1.0 versions (actually I believe pre-0.9.x, pre-Blaze) had this multiple rendered issue. Clever workarounds were required to make sure the rendered only fired once with these versions, but with Meteor versions available now (1.x.x), this has been resolved, which was very much celebrated at the time of its resolution! :tada: Template.onRendered only fires once. :smile:

@captsaltyjack – I just implemented your Tracker.afterFlush idea in my this.subscribe callback and works like a charm! Thanks, saved me a ton of time!!


#17

Glad to hear it! It’s come in very handy for me more than once.