Patterns and practices for passing data between templates

I’ve read that elsewhere; why then the wrapper-ReactiveDict, and why does ReactiveDict not survive a HCP (or is that the difference)? But to continue along this line of thought, why not the inverse – why not use ReactiveDict instead of Session?

To me, this term means do something [on] (just before) the [creation] of, whereas just [created] could mean after something has been created.

I must have misunderstood what you’re using Session for. Your scope seems bigger. I’m intrigued, are you talking about this scenario?

Template1 -> Collection1
=> user makes modifications to Collection1 for example
=> via a Event on Template1 stores the results of your modifications to Collection1 in Session1
=> in the Event calls Router.go(‘Template2’)

Template2 -> Collection2
=> OnCreated function checks for the Session1 and now has the data from Template1
** => are you saying that at this point you make modifications to Collection1 that is in Session1 on Template2 and do a upsert of Collection1 via an Event on Template2?

Will you sketch this out please?

Thanks for the continued conversation.

It started out as Session, and ReactiveDict was extracted out of it (to create ReactiveVar, I think). Session is retained for backwards compatibility and has QA tests on it. I find ReactiveDict to be klunky and Session to be semantically easier to think with; it’s mostly personal preference though.

And yes about the distinction with [on]Created. The ReactiveDict/Session variable has the right functionality to pass the data around, but naming it in the past tense (corresponding to an event having happened) is bound to become semantically confusing, since its in the global scope and many things can set it.

Round-Robin (as I understand it)

Request for 'foo/12345’
Router sets data context for Template.foo with Foo.findOne(this.params.fooId)
activeFoo session variable is set in route action (dev has to wire this up)
Collection.foo renders with data context (or activeFoo)
headerTemplate and footerTemplate update by way of activeFoo
A button click on footerTemplate opens Template.bar
Events on Template.bar update the activeFoo object
A button click on footerTemplate triggers Foo.upsert(Session.get(‘activeFoo’))

I’m on a tablet, which doesn’t seem to be recognizing the formatting options. Will clean the formatting up this evening.

But that’s the general use case that I’ve been running into over-and-over-and-over.

ReactiveDicts DO survive hot code push when they arre created with a name

1 Like

[EDIT1] Updated this post with more detailed code examples.

So there is no fear of the globals for you – I get that.

Using a Session to pass around Collections from one Template to another is interesting. But isn’t the ‘Meteor’ way to subscribe to all the Collections used on a Template by Template basis?

For example:

Template1 - uses - >
-> Collection1
-> Collection2

Template2 - uses ->
-> Collection1
-> Collection3
-> Collection2.user_id via Session set in Template1
-> Collection4 filtered on Collection2.user_id

In this case the Subscription would look similar to the following perhaps?

Template.Template1.onCreated(function () { 
  var self = this;
  self.autorun(function () {
    self.subscribe('Collection1');
    self.subscribe('Collection2');
  });
});

/**this Event along with the Session ties together Template1 and Template2*/
Template.Template1.events({
  'click li .user': function (event) {
    event.preventDefault();
    var clickedElement = event.target;
    Session.set('user_id', cickedElement.userId);
    Router.go('Template2')
  }
});

Template.Template2.onCreated(function () { 
  var self = this;
  var user_id = Session.get('Col2_User_Id'); <- this is Collection2.user_id via Session set in Template1
  self.autorun(function () {
    self.subscribe('Collection1'); <- use in Template1 also
    self.subscribe('Collection3');
    self.subscribe('Collection4', user_id); <- user_id from Template1 is used to filter collection returned from server
  });
});

In this way, each Template subscribes to the Collection it uses. In the case of Collection1, Meteor works out the most efficient data extraction pattern from the server. In other words, if there’s no modification of Collection1 from Template1 to Template2, there’s not extra call to the server to get the data for Collection1 on the Template2 Subscribe. I thought this was how it works – more or less.

It’s interesting to contrast the approach above with the one using a global variable to pass around a Collection1 from Template1 to Template2.

Staying with this example, below would be the Session way, is this what you had in mind?

Template.Template1.onCreated(function () { 
  var self = this;
  self.autorun(function () {
    self.subscribe('Collection1');
    self.subscribe('Collection2');
  });
});

/**this event along with the Session ties together Template1 and Template2*/
Template.Template1.events({
  'click li .user': function (event) {
    event.preventDefault();
    var clickedElement = event.target;
    Session.set('_Collection1Doc', Doc1); <- Being used by Template2
    Session.set('_Collection2Doc', Doc2); <- Since the userId is being used by Template2
    Router.go('Template2')
  }
});

Template.Template2.onCreated(function () { 
  var self = this;
  var collection2Doc = Session.get('_Collection2Doc); <- this is Collection2.user_id via Session set in Template1
  var user_id = collection2Doc.userId;
  self.autorun(function () {
    /** no need to subscribe to Collection1, it's in a Session */
    self.subscribe('Collection3');
    self.subscribe('Collection4', user_id); 
  });
});
1 Like

Sidenote A) I don’t think (someone will correct me if wrong) you need the autorun around all .subscribe() calls. Only around the ones that depond on one of your parameters. The rest can just self.subscribe() within the onCreated.

Sidenote B) I don’t think people ment passing around the whole collection in Session, just the selected ID. You react to that in template2.

Take much care though. As I said in the previous thread, depending on Session for this feels like a can of worms in some apps. The issue I have with it (amongst others :wink: ), is that it assumes you’ll use a Router (and thus depend on url). What if you wanted to open 2 views (template), both details views, but both with their own depending ID. Then the session will need to be some sort of Array to be able to hold more values (or generate the session key as a dynamic string… here come the worms) and you get back to the same problem of having to communicate the index of the appropriate ID for the given template within the array…

It will work, as long as you have 1 of everything max :smile:

I’ve use the Template instance based apporach for a while now. Works great. Only the syntax is really weird (with al the self.foo() declerations). The Blaze-components package does a really nice job of correcting that, but I ran into issues mixing that setup with normal Templates (regarding Blaze.renderWithData).

2 Likes

I believe for the most part this is in fact how it works. My post about subscriptions is a WIP geared towards providing a clearer understanding of how subscribes work.

In your particular example there are a number of nuances that are important. If in Template1 we do a subscription that ultimately returns Collection1 and then in Template2 we do the same subscription the server will not re-send Collection1 as long as both templates are active. However if Template1 is destroyed in Flush cycle before Template2 is created I believe the client will unsubscribe from Collection1 and purge it from the cache, thereby making the server send the data again. Storing data context in a Session can be similar to creating your own subscription manager like subs-manager and subs-cache.

One of the trade-offs with passing data context around like this is that you need all of the data for every context wish to use it for, where-as with this.subscribe and template subscriptions you just get the data needed. Also, when something updates the Sessions data, I don’t believe the template can intelligently re-render, it just re-renders everything within that data context and below (I could certainly be wrong about that).

I personally think a decent solution lies somewhere within the principals of:

  • Subscribe to the all of the data you anticipate the user will need as soon as you can make that anticipation… these subscriptions should not block any other DDP traffic/connections
  • Subscribe to the exact data you need to render template in the onCreated subscription… these subscription should block
  • Implement a helper specifically for passing the data to create the context you want within {{#with}} or {{#each}}.
  • If this helper or a generic version of it can be abstracted to the be a UI helper than do it it.
1 Like

[quote=“awatson1978, post:27, topic:2951”]
I’ve also been thinking about whether its best to store an _id, the entire json document, or a subdocument. I’m thinking that storing the subdocument is an antipattern, unless you have an enforced schema aka simple-schema. Which leaves _id vs the entire document, but the entire document includes the _id, so it’s just best to stuff the entire document in the session variable.
[/quote] Emphasis mine.

[quote=“awatson1978, post:31, topic:2951”]
with regards to Session scope creep, and thought ‘only store what I need’. But, in practice, it wound up creating a lot of subdocument parsing logic that wasn’t needed yet had to be maintained. Storing the entire document seems to be the cleaner solution from the perspective of the database round-robin, and works with the upsert() command.
[/quote] Emphasis mine.

Yes – Document, not Collection.

The thing that bites me with Session repeatedly is how they stick around. For some reason early on I thought that iron-router dumped session vars on each route, but it turns out they persist until you get rid of them manually.

For example I have a search input for users and another for products. At first I just used Session.set('searchTerm', value) but that gets me in trouble when switching from users list to products list since both use find({ name: Session.get('searchTerm')}) and thus returning no results when navigating from one section to another after searching in one without refreshing the page. At the surface and in tests everything looks like it should be working fine, but when actually using the app it creates the issue. Then I started naming my session variables really long names like AdminUsersIndexSearchTerm, but just feels bad to scope the variable using it’s name.

I switched over to using reactiveVar which does fix the scope issues but in some cases I had a hard time passing the reference to the var down the chain of child templates. This is about when I started looking at react so I just went down that road instead. Never really found a good pattern for blaze I was happy with when dealing with multiple parent > child templates on a single page.

3 Likes

Have been scrolling up and down to try and wrap my head around what makes this so hard (it seems straight forward). To sum my views up, I think it really depends on what your usage context is. Just as a general note, know that there is no “one good way” (e.g. assuming Iron Router is a Meteor way, it’s not.). (clarification: as in, it’s not always going to be used)

What I mean by “context” is e.g. these kind of use cases:

  • If you want to use url’s with an id in it and have 1 detail view -> Use Iron Router (or FlowRouter btw ;)).
  • If you have a Master/Detail view where both are in view always -> Probably store the ID of the selectedItem in a Session
  • If you want more dynamic views (a.k.a., more app feel than a page to page feel), I think you’ll need your setup to work with Template instances, and (someone correct me) you’ll end up with passing ID’s (or Document if you really want to) around from template to template.

And there will probably be more cases. So it really depend I guess.

3 Likes

Ah, thanks. Learn something new.

It’s not that I encourage or prefer globals. I’ve simply made my peace with them. (It should be noted, btw, that Session gets placed in the Meteor application scope, not the actual global scope of the interpreter.)

Ideally, I’d like to see mitar’s solution get adopted, so we could do something like:

FooListPage extends MyDataSurface extends BlazeComponent
FooFooterTemplate extends MyDataSurface extends BlazeComponent

And then we could attach Session/ReactiveDict/ReactiveVar objects to the MyDataSurface object scope, instead of the Meteor scope.

Thanks for this. Will you expand on what this means exactly, and say why this distinction is important?

For application scope read: http://docs.meteor.com/#/full/namespacing Basically meteor provides its own namespace. And I’m sure you already know the reasons why not using global scope is encouraged.

I am a bit confused though, I thought Session is a globally scoped and attached to window.

Not 100% sure if this is what you mean, but you can communicate between helpers and event handlers (template logic) by storing state in a number of ways. As a matter of policy I avoid Session in almost all cases. I feel their global scope leads to bad habits and lack of good discipline regarding separation-of-concerns as your application grows. Also because of their global scope, Session can lead to trouble when rendering multiple instances of a template. For those reasons I feel other approaches are more scalable.

Alternative approaches

#1 addClass/removeClass
Instead of setting a state then reacting to it elsewhere, can you perform the needed action directly. Here classes display and hide blocks as needed:

'click .js-edit-action': function(event, t) {
    var $this = $(event.currentTarget),
    container = $this.parents('.phenom-comment');

    // open and focus
    container.addClass('editing');
    container.find('textarea').focus();
},

'click .js-confirm-delete-action': function(event, t) {
      CardComments.remove(this._id);
},

#2 ReactiveVar scoped to template instance

if (Meteor.isClient) {  
  Template.hello.created = function () {
  // counter starts at 0
  this.counter = new ReactiveVar(0);
};

Template.hello.helpers({
  counter: function () {
  return Template.instance().counter.get();
  }
});

Template.hello.events({
  'click button': function (event, template) {
    // increment the counter when button is clicked
    template.counter.set(template.counter.get() + 1);
  }
});
}

#3 Iron-Router’s state variables

Get

Router.route('/posts/:_id', {name: 'post'});

PostController = RouteController.extend({
  action: function () {
    // set the reactive state variable "postId" with a value
    // of the id from our url
    this.state.set('postId', this.params._id);
    this.render();
  }
});

Set

Template.Post.helpers({
  postId: function () {
    var controller = Iron.controller();

    // reactively return the value of postId
    return controller.state.get('postId');
  }
});

#4 Collection data
Another approach is to simply state by updating data in your collection. Sometimes this makes perfect sense.

#5 update the data context
Sometimes it makes sense to just add data to the context. But may not be appropriate when using iron-router as it could rewrite the data context on you.

#summary
Session is often the worse choice in my opinion. Also I don’t personally use #3 as I feel like being less tied to iron-router is better incase we ever want to switch to another router package such as “Flow”.

1 Like

Apart from a dispatcher pattern, I’ve been using a design where I directly set template data scope {{>myTemplate dataScope}} but also augmenting (injecting) a reactive var/dict from another template into that scope dataScope.dependency=this.sate.get() (also requiring a helper within a parent template which encloses such patterns just for the enclosure to manage multiple possible scopes) hence achieving inter-template reactive communication. Not a silver bullet, but works great for many situations.

Also surprised this is not mentioned above, but you can use parentData to “Accesses other data contexts that enclose the current data context.”

http://docs.meteor.com/#/full/template_parentdata

1 Like

I read this too, but my interpretation was that this is used for a Template within a Template scenario… today I do this with {{…/…}} (like the command line) as the example suggests is an alternative.

/** This is poseo code as I could not find an example... the exact syntax could be wrong.  If I have time later (and no one has done it already), I'll try to tighten this up. */

<template name='parent'>
  {{> child param_1='test'}}
</template>

<template name='child'>
  {{#if child_helper param_1}}
    <div>{[helper_value}}</div>
    <!-- get parent context value -->
    {{> child_of_child ../param_1}}  <-- I think this is how it works... 
  {{/if}}
</template>

Template.helpers('child_helper', function (param1} {
  // do stuff return stuff
  return helper_value;
});

Blaze.getView() is also very powerful. If one designs the templates so that it can be determined by one or few seletors, the template’s first enclosed dom node can be found and that can be used as a reference to get the view, from which templateInstance() would be accessed.

Not very clean, but it also works regardless of the relationship between templates.

1 Like

Completely agree with this sentiment.


When I first started with Meteor I used IR for a lot, but as time goes by I’m pulling back from this as much as I can – for many reasons.

I simply do not want to use IR’s state and do not want to pass the parameter around in a URL. Yet, this view is only a personal preference.

This is a perfectly valid way to pass data from one Template to the next using under the appropriate circumstances.

1 Like

[quote=“funkyeah, post:44, topic:2951, full:true”]I am a bit confused though, I thought Session is a globally scoped and attached to window.
[/quote]

As I understand it, Session and Template and the other API objects all go through the same bundling process, which wraps each file in a closure, and is what causes variables without ‘var’ to behave in a non-local manner. The bundle process takes all the files and attaches them to the Meteor application scope, and keeps a special eye out for Meteor.startup() clauses, which it puts at the top of the file. The Meteor object is what then gets eventually attached to the window. Session may look like it’s global and attached to the window when one is writing JavaScript, but the bundle process boxes and wraps all the JavaScript in a Meteor object. That’s how I understand things, anyhow.