Ensuring all DOM elements available before populating (FlowRouter & Blaze)


#1

Hi all,

Just a quick question about what the best pattern would be for using Template level subscriptions with FlowRouter/Blaze.

We recently migrated an app from Iron Router to Flow Router but we sometimes see an issue. In our new Flow Router router.js file we no longer have any subscriptions or data contexts being created and passed. All our subscriptions now happen in the templates in the ‘onCreated’ callback. In our Blaze templates near the top of the HTML we now always have an ‘{{#if Template.subscriptionsReady}}’ block rendering the main code for each page. If this call fails we pass into rendering a ‘{{> loading }}’ template. Now on some of our pages we need to populate certain HTML data fields on the page. This code would normally be put in the ‘onRendered’ callback in the template.

However what we are sometimes seeing is that the code starts trying to go through this ‘onRendered’ code even though the HTML elements are not actually yet available. It looks like what is happening is that when the code passes through the ‘{{> loading }}’ path that satisfies the criteria for the ‘onRendered’ callback to run which then triggers the code to populate the HTML elements. However the HTML elements would only ever be rendered when we do not go through this path…

Does anyone have any advice about the best pattern to use in these situations? At the moment we can kind of hack around it by moving our HTML population code into a helper function which is run within a ‘Meteor.defer’ block which does seem to be a workaround that works but it doesn’t feel like the best solution…

Thanks in advance,
Rick


#2

An example would be interesting


#3

I ran into the exact same issue (even with Iron Router). If you think about it, it actually makes sense, because your template has been rendered. I came up with the following solutions

I resorted to Template.dynamic or sub-templates.

I do all my subscriptions in Template.customers.onCreated

<template name="customers">
 {{#if Template.subscriptionsReady}}
  {{>customersList}}
 {{else}}
  {{>loading}}
 {{/if}}
</template>

for some routes I use a dynamic sub-template and switch based on the route name

<template name="userProfile">
 {{#if Template.subscriptionsReady}}
  {{>UI.dynamic template=subTemplate data=data}}
 {{else}}
  {{>loading}}
 {{/if}}
</template>

<template name="userProfileShow">...</template>
<template name="friendProfileShow">...</template>
Template.userProfile.onCreated(function(){
 var instance = this;
 var current = Router.current();
 var name = current.route.options.name;
 switch(name) {
  case 'userProfile':
   instance.subscribe();
  break;
  case 'friendProfile':
   instance.subscribe();
  break;
 }
});
Template.userProfile.helpers({
 subTemplate: function() {
 var current = Router.current();
 var template = 'notFound';
 var name = current.route.options.name;
 switch(name) {
  case 'userProfile':
   template = 'userProfileShow';
  break;
  case 'friendProfile':
   template = 'friendProfileShow';
  break;
 }
 return template;
 },
 data: function() {
 var current = Router.current();
 var data = null;
 var name = current.route.options.name;
 switch(name) {
  case 'userProfile':
   data = Meteor.users.findOne(current.params.userId);
  break;
  case 'friendProfile':
   data = Friends.findOne(current.params.userId);
  break;
 }
 return data;
 }
});

Router.route('/user/profile/:userId',{
 template: 'userProfile',
 name: 'userProfile'
});
Router.route('/profile/friend/:userId',{
 template: 'userProfile',
 name: 'friendProfile'
});

#4

Hi @jamgold,

Thanks for your reply. Actually you are perfectly correct. The template is effectively rendered even if the Template level subscriptions are not yet ready and the code path goes through the ‘{{> loading}}’…

So I think your method definitely does help by moving out all the actual template code into a sub-template which will have it’s own ‘onRendered’ function… It does make me feel that this way does introduce another ‘layer’ between the router and the actual templates which I am still not sure is the best way but it may be a better way than my current workaround which involves a ‘Meteor.defer’…

Cheers for your help!
Rick


#5

Hi,

We use a reactiveVar set to true into onCreated when subscription is ready and an autorun inside onRendered that will do the job when the reactiveVar is true. Then you can stop both autoruns.


#6

Hey @ixdi

Thanks for the idea… do you have an example of how that looks? Also not sure what you mean by stopping both autoruns?

Thanks in advance,
Rick


#7

Example:

Template.Name.onCreated(function() {
    let self = this;
    self.ready = new ReactiveVar(false);
    self.hSubs = this.subscribe('publishSomething');
    self.autorun((c) => {
        self.ready.set(self.hSubs.ready());
        if (self.ready.get() === true) {
            c.stop();
        }
    });
});

Template.Name.onRendered(function() {
    let self = this;
    self.autorun((c) => {
        if (self.ready.get() === true) {
           // do something with DOM. Maybe need to call Meteor.defer()
           c.stop();
        }
    });
});

check ready reactiveVar with a template helper. We use common registrerd helper to check it.
It’s also possible to check directly hSubs but we avoid this solution as sometimes subs is refreshed in our app.