Good techniques for waiting till data is absolutely finished loading

I have have been using onRendered, to wait til Meteor has finished with a combination of various other things like Tracker.autorun, setTimeout and document.readyState. The problem is I find that this does not always work as a full proof solution with events firing to early.

For instance if I render an object to my template from a helper and then I would run a loop to apply a series of styles to the object that are now DOM nodes. But more often than the event fires early and the data comes later.

With Tracker.autorun it is not always a great solution because the events are not reactive data. document.readyState has the same issue as Tracker.autorun and is a bit more bloated in code than I want. And well setTimeout is just bad for UX in this instance because of the arbitrary wait time that is unnecessary to render the UI.

I have also tried attaching or tapping into values in the Template object, couldn’t come up with anything.

Thoughts?

This is an important thing to get right. I had just this same problem with my app recently and found a good solution. In my case I was rendering intermediate stages of the data being ready. I wanted to hold off rendering until all of my data was loaded.

Here is how I handled it:

  1. I created an autorun for each category of data that I wanted to verify was ready.
  2. For each category of data I was monitoring, I created a reactive variable that would be set to true when my data was processed.
  3. I created a ready() function that would return the result of all of my data categories being ready.
  4. I created a template helper function isDataReady.
  5. On my template, I surrounded the HTML with an {{#if isDataReady}} ... {{/if}}.

Now, none of my templates try to render until all of my data is available. No more intermediate states.

In my case I had a 2nd problem. On the server, my admin would load data from files. As the files loaded the resultant data structures would start publishing and publish a few times until all the data was finally loaded.

To stop the intermediate publications I started using Mongo bulk operations. To gain access to them I used the same trick as the meteorhacks:aggregate package. When I use bulk operations the reactive functions (e.g. publish functions) only fire when the data is fully loaded or updated.

Works great for me. I hope you have the same luck!

1 Like

here is an intersting link on meteor reactive properties.

http://stephenwalther.com/archive/2014/12/05/dont-do-react-understanding-meteor-reactive-programming

This is quite outdated but it still applies and the code is available in github. It helped me a lot to understand this issue and find solutions.

1 Like

Hi @brucejo,

Would you be able to share some sample code? Would you happen to know if same technique would apply to an Meteor Angular2 project?

Thanks.

Hi @redpocket,

I am not sure if a Meteor Angular2 project would work the same way, but I suspect the concept is reusable.

Do you use Meteor pub/sub in your app?

Here is a quicky example:

let dataSet1 = {};
let dataSet2 = {};
let dataSet3 = {};

// In my case I have some global data that I needed to process
// you can do the same thing with data only specific to a template

function getData() {
  dataSet1.sub = subscribe('dataset1');
  dataSet1.ready = new ReactiveVar(false);
  dataSet2.sub = subscribe('dataset2');
  dataSet2.ready = new ReactiveVar(false);
  dataSet3.sub = subscribe('dataset3');
  dataSet3.ready = new ReactiveVar(false);

  // autorun for getting the data and handling it
  Tracker.autorun(() => {
    if(dataSet1.sub.ready()) {
      // process dataset1
      // after processing set ready variable
      dataSet1.ready.set(true);
    }
  });

  // autorun for getting the data and handling it
  Tracker.autorun(() => {
    if(dataSet2.sub.ready()) {
      // process dataset2
      // after processing set ready variable
      dataSet2.ready.set(true);
    }
  });

   // autorun for getting the data and handling it
  Tracker.autorun(() => {
    if(dataSet3.sub.ready()) {
      // process dataset3
      // after processing set ready variable
      dataSet3.ready.set(true);
    }
  });
}

function dataIsReady() {
  return dataSet1.ready.get() && dataSet3.ready.get() && dataSet2.ready.get();
}

// Then in my Templates I can do this
Template.myTemplate.helpers({
  'dataIsReady': () => {return dataIsReady();}
});

Then in your html:

<template name="myTemplate">
  {{#if dataIsReady}}
  <!-- Do my stuff only when data is ready -->
  {{/if}}
</template>
1 Like

Wow thanks for the quick response @brucejo.

Yes I am using Meteor pub/sub. I will give this a shot tomorrow.

Cheers!

@brucejo I tried the following snippet:

       var handle = Meteor.subscribe("campers");
        Tracker.autorun(() =>{
            if (handle.ready) {
                this.campers = Campers.find({guardian:Meteor.userId()}).zone();
                this.showCampers = true;
            }
        });

In my html I check if showCampers is true.

What I’m noticing is that the handle appears to be ready, however the data in this.camper isn’t populated until I hard refresh the browser.

Any ideas why this might be happening?

hi @redpocket, not sure if you have a typo in your example. But handle.ready should be handle.ready(). handle.ready should always be true because it is a function.

Hi @brucejo,

You are correct. I did have a typo. I’m still running into the same issue where the view is not refreshing. I’ve added a function that I check in the template the condition and returns true/false. Still no luck.

@redpocket could you show us your template code you are using together with your provided snippet?

yes, also show us your publish function too.

Just wanted to say thank you for this answer, I ended up using your processes, but in this fashion:

// item-details.js
Template.item_details_template.onCreated(() => {
	const self = Template.instance();
	const _id = FlowRouter.getParam('_id');

	self.sub = self.subscribe('items.single', _id);
	self.ready = new ReactiveVar(false);
	self.item = new ReactiveVar();

	self.autorun(() => {
		if (self.sub.ready()) {
			self.ready.set(true);
			self.item.set(Items.find().fetch()[0]);
		}
	});
});

Template.item_details_template.helpers({
	dataIsReady() {
		return Template.instance().ready.get();
	},
	itemDetail() {
		return Template.instance().item.get();
	},
});
<!-- item-details.html -->
<template name="item_details_template">
    <section class="item-details-template">
        {{#if dataIsReady}}
            {{#with itemDetail}}
            <div class="details">
                <p>one sexy looking page!</p>            
            </div>
            {{/with}}
        {{else}}
            {{> loading}}
        {{/if}}
    </section>
</template>