Patterns and practices for passing data between templates

Thanks for the write up. I’ve tried a similar approach as well, but again got bitten by the Session problems I described above. For example in your app if there were 2 routes, and you selected Ke, went to the second route and came back, Ke would still be selected. In some cases that is the desired behavior, but not in mine.

Here’s a shot of what I am trying to figure out the best way to setup.

We have a list of messages, selected message displaying, and changing content within the selected message. My situation is almost the same, it’s a searchable list of users on the left, currently selected user profile displaying on the right, and tabs on the top of that right section switching which content (profile, billing, messages, etc) of the current user I am interacting with. With session (and reactiveVar as well, I think) if we navigate away from that route and come back, everything will still be loaded up (selected user, billing section, edit billing details since none of those variables change. I would have to manually set them all to null values when the route loads, which doesn’t seem like the best way to do things.

If I could figure this piece I out I without relying on passing Session variables around I would be so much happier!

I’m finished up a small test case for passing values between Templates, now using both Session and ReactiveVar. I’ll post the full code and explain my rational after I figure out a few details.

The following is the Message Details Template subscription and helper function:

Template.user_messages.onCreated(function () { 
  var instance = this;
  instance.user_id = new ReactiveVar(Session.get('selectedUser'));
  instance.messages = new ReactiveVar({});

  instance.autorun(function () {
    var user_id = instance.user_id.get();
    instance.subscribe('user_messages', user_id);
  });
});

Template.user_messages.helpers({
  user_messages: function () {
    var user_id = Template.instance().user_id.get();
    return user_messages.find({user_id: user_id}, { sort: { posts: 1 } }); 
  }
});

Will someone please explain to me why, when I open two different browsers in the console, and select two different users, I see two different values for Session?

I thought Session was global and instance transendant – am I wrong? Can this be because I’m setting the ReactiveVar to the Session value on each instance?

Browser1

> Session.get('selectedUser')
< "123"

Browser2

> Session.get('selectedUser')
< "456"

I’m finished up a small test case for passing values between Templates, now using both Session and ReactiveVar. I’ll post the full code and explain my rational after I figure out a few details.

The following is the Message Details Template subscription and helper function:

Template.user_messages.onCreated(function () { 
  var instance = this;
  instance.user_id = new ReactiveVar(Session.get('selectedUser'));
  instance.messages = new ReactiveVar({});

  instance.autorun(function () {
    var user_id = instance.user_id.get();
    instance.subscribe('user_messages', user_id);
  });
});

Template.user_messages.helpers({
  user_messages: function () {
    var user_id = Template.instance().user_id.get();
    return user_messages.find({user_id: user_id}, { sort: { posts: 1 } }); 
  }
});

Why use ReactiveVars at all for passing data between Templates?

In the following case the Session is just a reactive as above:

Template.user_messages.onCreated(function () { 
  var instance = this;
  instance.autorun(function () {
    instance.subscribe('user_messages', Session.get('selectedUser'));
  });
});

Template.user_messages.helpers({
  user_messages: function () {
    return user_messages.find({user_id: Session.get('selectedUser')}, { sort: { posts: 1 } }); 
  }
});

If there’s worry about, after moving to another screen (in my case Template), and having the Session variable sitting around with a value that’s no longer needed, we can always delete it after the Template is flushed:

Template.user_messages.onDestroyed(function () {
  Session.set('selectedUser', null);
});

In the 1.1.0.2 Meteor Docs, it states one difference between Session ReactiveVars is:

ReactiveVars don’t have global names, like the “foo” in Session.get(“foo”). Instead, they may be created and used locally, for example attached to a template instance, as in: this.foo.get().

For the purposes of passing data from one Template to another, I can’t see this as having much value.

If this is this one of the main selling points, I think in terms of passing data from Template to Template, I agree with @awatson1978, using Session seems like it is the way to go.

Session variables are only global to the current browser, not shared across browser instances. Could you imagine if everyone who is using the app would be forced to share all session variables? Someone searches while I’m visiting the site and all the results are displaying their search, so I start typing and theirs gets mixed with mine? That’d be pretty nuts.

Session (as far as I know) is global to your app but limited to the current browser “session”, ie without refreshing the page. You should check out https://atmospherejs.com/msavin/jetsetter and https://atmospherejs.com/msavin/mongol, very handy ways of seeing your meteor variables “behind the curtain” so to speak.

1 Like

Thanks @tehfailsafe, I’ll check this out!

But, at least you can do something like:

Template.name.onDestroyed(function () {
  Session.set('selectedUser', null);
});

Which will flush out the Session after the Template is flushed.

Like others have said, this is a case where name spacing is everything. users.searchTerm, and products.searchTerm come to mind.

Yes, but even so, I still believe it’s best to let Meteor mange this. I don’t want mange the collections.

Interesting. A good reproduction on github would allow us to investigate.

I think I sorted this out in post #64.

There is a list of people in Template1. This list has an [ID] of a unique person embedded within an element on that page. When the user clicks a person, they should be taken to a message detail screen for that user.

OK for this case, why not pass the ID in the URL as a query string param?

Why not pass the ID in the url string? Great way to organize your app as you’ll have clean, meaningful and stateful urls:
myapp.com/cards
myapp.com/card/321

Could be reading it wrong, but I don’t think your did. In the sense that (am I reading it right?) it still depends on a single Session variable right? So it’s still not possible to show 2 detail views on the same screen?

I created a fully working demo for you which demonstrates what you are asking without using Session.

I almost always avoid Session. I think it pollutes the global scope. Also it prevents you from running multiple instances of the template. I recommend using a reactiveVar or reactiveDict scoped to the template instance.

attach a reactiveDict to the template instance onCreate. Use this to store state instead of global Session var!

Template.Restaurants.onCreated(function() {
  this.state = new ReactiveDict;
  this.state.set('currentRestaurant', null); // could set a default value here
});

this event handler will set the state of the reactiveDict on click

'click': function(e, t) {
    t.state.set('currentRestaurant', this._id);
}

this helper is used to show/hide the menu template

currentRestaurant: function() {
// check whether this restaurant is selected. "this" refers to the current
// context, eg. the current restaurant in the loop
return Template.instance().state.equals("currentRestaurant", this._id);
},

menu template receives the selected id from data context instead of from Session

<template name="Restaurants">
  <ul>
    {{#each Restaurant}}
    <li>
      {{name}}
      {{#if currentRestaurant}}
      {{> menuItems restaurant=_id}}
      {{/if}}
      </li>
    {{/each}}
  </ul>
</template>

<template name="menuItems">
  <ul>
    <li class="menu">I'm a menu for {{restaurantName}}!</li>
  </ul>
</template>

added this helper just to show we really got the id

Template.menuItems.helpers({
  restaurantName: function() {
    var restaurantMenu = Restaurants.findOne(this.restaurant);
    return restaurantMenu.name;
  },
})

Posted a fully working project to github.

App is hosted on meteor.com
http://scopedreactitivydemo.meteor.com/

3 Likes

Cool example, great stuff. But I don’t think this (totally) solves the initial issue. The problem the original poster had, was what to do in absence of parent/child relationships. In that case it get’s more complicated and all the different solutions mentioned in the thread are coming into play.

p.s. don’t know if i mentioned this before, but this problem only exists, because Blaze is more a template solution (now) than a true component solution. I.e., blaze-components and/or flow-components packages will probably tackle this issue (finding references to arbitrary other components).

3 Likes

You are right that my case isn’t exactly the same as yours, but I think what I did could be extrapolated to your situation. I would use Session.get(‘details1.userId’) and a Session.get(‘details2.usrId’) – or whatever made sense name wise. No need to restrict yourself to just one Session variable.

But what does this actually mean? How does using a global variable to pass an [ID] from one Template to another actually pollute? Especially if I null out the value after the receiver Template is done with the variable.

I don’t understand this as it reads here. Do you mean running the same Template side by side at the same time in the Browser?

How does this solve communication between Templates? Doesn’t using template-scoped variable mean the value does not live outside of the Template? How then would the value escape the template-scope to be pass to the next Template?

1 Like

I don’t want to make people run in circles, but I think this brings up with what I mean with “Using dynamic variable identifiers only moves the problem”. E.g., how will you tell which template should use which variable identifier :slight_smile: Exact same problem, only instead of “How do I pass around ID’s to templates” it becomes “How do I pass around Session key identifiers to templates” :smile:

My 0.02

Thanks for sharing, this is awesome!

This seems to be a good pattern for parent/child Templates. I’ll definitely look at using this pattern in place of my current method for dealing with parent/child templates.

Also, I think this post is the right place to add this type of Template communication, so thank you again for adding this to the discussion.


I also think there is another kind of Template communication, where one template is not embedded within the other.

In your example, simply by placing menuItems within the Restaurants Template, you created a relationship and scope that can be used for communication.

In your example the ‘child’ Template menuItems will actually render and is in the scope of parent Template Restaurants. This certainly qualifies as Template to Template communication. There are a few patterns for passing data around like this I’ve seen. I really like, and have never seen, your pattern here.

What about if the menuItems Template was not embedded within Restaurants? What if the listing for Restaurants was a table with a hundred Restaurants? Instead of the menu items for that Restaurant displaying within the same screen as the Restaurants list as your example demonstrated, when you clicked on a Restaurant you were swept away to another screen on another route, one where there is a vast array of menu items for that one Restaurant.

In this case there is no parent/child scope – one template knows nothing about the other unless you wire that relationship up. You need a way to communicate, i.e., pass data, from one Template to the next.

You can wire these relationships up in a few ways that I know about. One that you’ve posted about before is to use Iron Router to pass an ‘:_id’ from one Template on a route to another Template on another route. This is a fine pattern, that’s very commonly used.

But there are others, and for good reason. What if you didn’t just want to pass an _id, but an entire Document? That would be more difficult to pass via a Iron Router route I would think – I have not seen an example of this. What is an alternative way to get data from one Template to the next?

You need a variable. Just like you need a variable to pass data from function to function, you can have a variable pass from Template to Template.

As far as I know there are two ‘kinds’ of variables at our disposal. We have scoped, e.g., ReactiveVar and ReactiveDict, and we have un-scoped or global, i.e., Session. Scoped-template variables are just that, scoped to a Template. So the way I see it, they are not the right vehicle for passing data outside of the Template scope. This leaves us with one option, Session.

I no longer have a problem passing a Session variable from one Template to the next to facilitate communication, i.e., data transfer. In fact, is probably the only case, so far, that I see Session as a good choice.

In most cases probably welcomed, true. As long as you can live with the downsides, this is the most straightforward way. But for future reference, it’s not the only way. Especially with upcoming component frameworks.

In the future I’m guessing syntax like this will be more common as well (as soon as the component part of Blaze is mature):

<template name="master">
{{> EmailListComponent id="list"}}
{{> DetailComponent id="detail1" email="list.selectedItem"}}
{{> DetailComponent id="detail2" email="list.selectedItem"}}
</template>

Note the reference to “EmailListComponent” in the other components. This is currently (as far as I know) not possible in plain Blaze.

Hi @aadams,

Could you create and post the simplest example of what you are trying to accomplish? I feel like I could probably come up with a straightforward solution to most problems, and that the real problem is that I don’t have a clear picture of your use-case. Do you have an actual problem you are trying to solve, or is it more like a vague idea that you are wondering about?

What if you didn’t just want to pass an _id, but an entire Document?

This is Spacebars 101. You can pass an object to an inclusion template as a parameter, or by providing it in a data-context using Each or With.

pass the id in the querystring, then use the id to get the document from the collection. data everywhere remember?

I see what you mean, but I’d really like to see an example app of when you’d have two templates which are related enough to be on the same view, but so unrelated that passing data between them is problematic. i.e. can’t the data context be used to bridge things using all the normal ways we have to do that (passing params to inclusion templates, using Each or With, etc.)? I think best for him to post a repo as it seems like neither of us understand exactly what the OP is asking :wink:

Maybe this is where the confusion is,

The Templates in my examples are not in the same view – far from it.

Two different Templates, two different routes, two different screens/views – no relationship other than a route parameter or Session variable.

After reviewing the options in the feedback here on this thread and doing more research, I’ve picked Session variable over route parameter for this purpose – even knowing that choosing this path makes me a scope polluter. :smile:

1 Like

So what was the use-case? what is the nature of the templates and why are you trying to pass between them? You really have me curious :wink:

Every situation is unique so it’s hard to say what is best.

Maybe a similar case we had is having to “pass” a shopping cart from the addItem page, to the cartSummary page and to the checkout page. Completely different routes/templates. What we do is stick the cart data into a carts collection and retrieve it at each step based on the userId. If they lose their session or switch devices, they get their cart again once they login.

But yeah no reason to be fundamentalist about it. Session is certainly very quick and easy and find for small apps, but you might find it’s usefulness comes with some drawbacks as your application grows. As a rule, whenever I think I need a Session var I try and think of a way to avoid it. Nearly always the non-Session-var approach is cleaner in terms of architecture, maintenance and possible side-effects.