FlowRouter/BlazeLayout: How to set data context for a template on routing?

I’m using FlowRouter and would like to set a specific data context for my template on routing to this template via BlazeLayout.

According to the FlowRouter documentation, the router itself does not allow to set a data context while routing, and the docs say:

“We believe, it’ll possible to get data directly in the template (component) layer.”

This is OK for me, but how exactly?! The tutorials I found about Templates and Data Contexts (like https://www.discovermeteor.com/blog/a-guide-to-meteor-templates-data-contexts/) rely on IronRouters data context capability, so they won’t help me.

I’m also puzzled by the fact that most tutorials are assuming that either there is a subscription from which the client draws its data, or each single bit of data used in the template is provided by a template helper.

But how shall I proceed if I would like to retrieve data from some other source (like a 3rd party API) and would like to bind this data as a context to the entire template? I assume this might be a case for the Template.onRendered or Template.onCreated methods, but I’m also a bit unsure how to use these methods in a sensible way.

Is there any docs or tutorial available using FlowRouter / BlazeLayout in combination with setting a data context for the template to be displayed? This would help me a lot.

Thanks, Tom

1 Like

Hi,

Off the top of my head you could do as you suspect and stick a meteor method call in Template.templateName.onCreated()

The method would make the call to the third party API

In the callback of the method you could set a Session/ReactiveVar with the result

Template.templateName.onCreated(function() {
   Meteor.call('thirdPartyAPI', function(error, result) {
      Session.set('result', result);
   });
});

Then on the template side you could have:

{{#with result}}
   Content that requires a context
{{/with}}

And you would have a template helper that returned the Session/ReactiveVar, e.g.

Template.templateName.helpers({
  result: function() {
    return Session.get('result');
  }
});

Is that the sort of thing you were after?

2 Likes

Hi @tomwasd, thanks for your quick reply, highly appreciated. This pretty much describes in code terms the “raw and confused ideas” I had in my mind, so thanks a lot for putting this down as code samples.

One thing I was wondering about this approach is if there was any means of getting rid of the {{#with}} block here, i.e. a way to set the data context of the entire template without using a template helper for a with block? This is rather “cosmetics”, but would make the code a bit cleaner and more re-usable, as the context can be set from the “outside”. As far as I understood it, this is exactly what IronRouter’s “data” directive does.

Of course, I could also use a surrounding template with {{> innerTemplate result}}, but this would only move everything one level higher in the template hierachy, since still the outer template would have to set the result via a template helper.

Is it possible to set the entire data context of a template instance right inside the .onCreated() or .onRendered() methods, before the template variables are actually resolved by Blaze?

Well in pure (expressive) Blaze, you cannot do that. Iron router is known to do that by using its own layout manager and wrapping the template with a data context automatically.

But flow router already has a set opinion that a data context does not belong to the router.

Therefore, you should surround your content block with a with

As a side note, yes it is possible to actually provide a template a data context from blaze layout (by kadira) and it is strictly a pattern that is recommended against. If you must know, here it is:

<template name="layout">
  <h1>My App</h1>
  {{Template.dynamic template=main data=data}}
</template>

<template name="postContent">
  <h2>Post Content for {{title}}</h2>
  <div>{{body}}</div>
</template>
FlowRouter.route('/posts/:_id', {
  action: function(params) {
    var post = Posts.findOne({_id: params._id});
    BlazeLayout.render('layout', {main: 'postContent', data: post})
  }
});

But then you’ll face reactivity issues so you’ll also need to use Tracker and will also face unnecessary template rerenders.

So, basically using with is also better in terms of overall cosmetics.

1 Like

@serkandurusoy: Thanks a lot for this good explanation! I did not know yet that Template.dynamic has a data property to set the data context, good to know.

I understand that FlowRouter has the opinion that data contexts shouldn’t be set by the router, and I respect this. Yet, I’m still wondering how it would be possible to loose-couple things then, i.e. how I could prevent that the template must explictly know how to retrieve its own data context. This seems only possible using the (not recommended) approach you’ve described above.

I will follow FlowRouter’s recommended approach for now (and trust in the genius of “professor” Arunoda), but IMHO this limits re-usability of a template to some extent. Well, I’ll see if this causes any coupling problems in the future. So far, it will work for me.

1 Like

On the contrary, providing data context to template with an inclusion tag is much better in terms of decoupling. This way that template will not have to be tied to the route. Remember, a route can resolve to a page with multiple templates which require separate data contexts!

So the best approach to achieve portability of templates would be:

<template name="layout">
  <h1>My App</h1>
  {{Template.dynamic template=main}}
</template>

<template name="postContent">
  {{#with postData}}
    {{> postContentFull}}
  {{/with}}
  {{!-- 
    note that we could shortly have done:
    {{> postContentFull postData}}
  --}} 
</template>

<template name="postContentFull">
  <h2>Post Content for {{title}}</h2>
  <div>{{body}}</div>
</template>
FlowRouter.route('/posts/:_id', {
  action: function(params) {
    BlazeLayout.render('layout', {main: 'postContent'})
  }
});

Template.postContent.onCreated(function() {
  var template = this;
  Tracker.autorun(function() {
    template.subscribe('singlePost', FlowRouter.getParam('_id'));
  });
})

Template.postContent.helpers({
  postData: function() {
    var post = Posts.findOne({_id: FlowRouter.getParam('_id')});
    return post;
  }
})

Now this way, we decouple what template the router renders onto the page and what that template renders within that page specifically.

We also now can reuse the inner template.

We could also make a postData(id) helper so that we could get arbitrary posts.

3 Likes

Thanks for this explanation! Makes sense.

1 Like

Doing the following sets the data context for the template…

Template.test.onCreated(function() {
    this.data = {
        test: 'data'
    };
});

But if you try using this data in the template it doesn’t work.

Can anyone explain why? Setting data contexts for templates this way (and not in the router as advised) makes sense to me but maybe i’m missing something.

I understand you can use template helpers to add data to templates but setting data context when the template gets created seems more intuitive?

this.data is a reference to template.data where it is explained on http://docs.meteor.com/#/full/template_data as:

It is updated each time the template is re-rendered. Access is read-only and non-reactive.

1 Like

@serkandurusoy thanks for clearing that up for me. #readthedocs :grin:

1 Like