{{#if Template.subscriptionsReady}} is firing too soon


#1

Hi,

TLDR: In my template file, the Template.subscriptionsReady is returning true and rendering even though the subscription is not ready.

I am having an issue with template level subscriptions, in that the Template.subscriptionsReady helper function is firing immediately.

Here is what is inside my template for loading an image.

<template name="cards_image">
  {{#if Template.subscriptionsReady}}
    <img class="detail__caption__media__image" data-src="{{mediaUrl}}/{{image.filename}}" width="100%" height="auto" alt="" />
  {{else}}
    <p>Wait!</p>
  {{/if}}
</template>

What I would expect to happen here is that the message ‘Wait!’ is displayed and then the image is displayed only when the image helper function I am using is ready. For reference, my controller is below.

What is actually happening is that the code is loading the img tag as if the subscription is ready, and completely ignoring the second case to display the message, despite the fact that the template is not yet ready grabbing the data from the image helper, it behaves as if it is.

'use strict';

/**
 *	Template.cards_image
 *	Callback function called automatically when the template has been created
 *
 *	@method created
 */
Template.cards_image.onCreated(function() {
   // Adding this in as a fake delay to highlighte the issue
   setTimeout(() => {
     //also tried this.subscribe('images', this.data.sys.id);
     Meteor.subscribe('images', this.data.sys.id);
   }, 5000);
});

/**
 *	Template.cards_image
 *	Callback function called automatically when the template has been rendered
 *
 *	@method rendered
 */
Template.cards_image.onRendered(function() {
  let target = this.$('img').get(0);

  this.autorun(() => {
    Dependencies.scrolled.depend();
    Core.helpers.lazyLoad(target);
  });
});

/**
 *	Template.cards_image
 *	Callback function called automatically when the template has been destroyed
 *
 *	@method destroyed
 */
Template.cards_image.onDestroyed(function() {
});

/**
 *	Template.cards_image
 *	Helper functions
 */
Template.cards_image.helpers({
  image () {
    Dependencies.resized.depend();
    let selector = {
      'asset_id': this.sys.id,
      'device': Device.name,
      'density.multiplier': Device.pixelRatio
   };
   return Core.collections.images.findOne(selector);
  }
});

For reference, the entire project is on GitHub at - https://github.com/matfin/annachristoffer/tree/lazyload

Has anyone else encountered this issue and if so, how did you solve it?


#2

Well, there is none template level subscription, so it is behaving correctly.
Not Meteor.subscribe, but this.subscribe.


#3

Hey - thanks for the reply. I also tried using this.subscribe(…) but have the same issue


#4

than try it on clear project, cause any package messing with subscription can cause this default helper behaving strange.
For example loading indicator on top of the page in our project is ruining it, so we use custom helpers.


#5

Hmm I’m not sure I understand… I don’t have any packages messing with the subscriptions.


#6

and if u want to simulate slow publication, do it on server in publication by

Meteor._sleepForMs(5000);

#7

Just tried that and it seems that the template rendered callback is fired immediately. I will need to rethink my approach to this problem.

The good news is that the second case is coming through, where it renders the text ‘Wait!’ and then the image. I added a sleep function to the publication function for the images collection - see below

Meteor.publish('images', function(...imageIds) {
   Meteor._sleepForMs(5000);
   return Collections.images.find({'asset_id': {$in: imageIds}});
});

#8

Just dont delay that Template.instance().subscribe in onCreated, that sounds as bad idea to me.


#9

No I wouldn’t do that - I just added it in for illustrative test purposes.


#10

Ok, so I came up with a solution that seems to achieve what I want, which I will go through now.

I changed the templates created function so it looks like this:

Template.cards_image.onCreated(function() {
  this.dependency = new Tracker.Dependency;
  this.handle = this.subscribe('images', this.data.sys.id, () => {
    this.dependency.changed();
  });
});

What I did here was create a dependency that fires when the subscription is really ready. This is to overcome the problem where Template.subscriptionsReady just goes ahead and renders the tag anyway even tough the subscriptions are not ready.

Here are the changes I made to the template rendered callback:

Template.cards_image.onRendered(function() {
  this.autorun(() => {
    this.dependency.depend();
    Dependencies.scrolled.depend();
    if(this.handle.ready()) {
      let target = this.$('img').get(0);
      Core.helpers.lazyLoad(target);
    }
  });
});

Because the rendered callback is fired as soon as the template is rendered, and not when the subscriptions are ready, I call depend on the dependency I set earlier in the created callback. This dependency gets fired when the subscription is really ready, and then it also checks the handle above to make sure everything is set before it attempts to dig into the lazy loading function.

What I had wanted to do was make sure the image was loaded with the correct path inside its data-src attribute before I attempted to do some DOM related work on it (basically setting the img src tag to the value of the data.src tag).

Finally, inside my templates html code, I changed the following

<template name="cards_image">
  <img class="detail__caption__media__image" data-src="{{mediaUrl}}/{{image.filename}}" width="100%" height="auto" alt="" />
    {{#unless Template.subscriptionsReady}}
      {{>partials_loading}}
    {{/unless}}
</template>

I did not use {{if Template.subscriptionsReady}} because the img tag would not be rendered in time even though the subscription ready event fired, so this approach ensures that the image tag is fully set up before I go to work on it.

It also means I can add in the loading template using {{#unless}} as opposed to putting it inside an if else statement.


#11

As far as I know, Template.subscriptionsReady works as expected and I have used the exact scheme you use dozens of times without any problem.


#12

While trying to use Template.subscriptionsReady in my project, I noticed that it returns ‘true’ even when there is no subscription available. I’m a beginner so I perhaps I’m missing something here but…

Check out this MeteorPad: http://meteorpad.com/pad/yBhL8HZ5MMzDjLAnE/Template.subscriptionsReady

The ‘subscriptionsReady’ turns ‘true’ as expected after apx 5 seconds. Now try to comment out all the code in /server/app.js. You will see that the ‘subscriptionsReady’ still becomes ‘true’ after apx 2 seconds, although there is clearly (?) no subscription ready.

Considered I’m not misunderstanding something here, is this behavior by design or is it a bug? It’s a bit confusing…


#13

Think of it more like: there are no subscriptions that are not ready, therefore subscriptionsReady is true.


#14

I see. Well, it would be great to see that information added to the doc. Would save a bit of headache :+1:


#15

Thanks!!!