Patterns and practices for passing data between templates

Use the router object to access the needed parameters via

route = Router.current();
route.params._userid

I would rework on onCreate to

Template.user_messages_detail.onCreated(function () { 
 var self = this;
 route = Router.current();
 self.userId = route.params._userid
 //or if you use ReactiveDictionary to hold state
 //self.states = new ReactiveDic();
 //self.states.set("userId", route.params._userid);
 //retrieve state via self.states.get("userId")

 self.autorun(function () {
    self.subscribe('user_messages_detail', self.userId);
 });
});

As the the different between pages/routes and non parent-child template, they are close by pages and routes are more holistic. All templates and component as a page or route as considered as a unit of task. Ex. Post Details, focus on all this details of post. Within routes/pages, you might have multiple non parent-child template, example, editPost dialog on postDetail page/route. The dialog can be initiated by multiple edit buttons on the postDetails page. Thus, one dialog, multiple points of instantiation, so the initiator template and the dialog template need to communicate but they don’t have a parent-cild relationship, it more of an action relationship.

Take a look at Inter component/template communication (architecture)

1 Like

Thanks for that. But how would I go about getting the _userid into the router object?

If you are using iron-router or any of the meteor routers, they provide api’s to access parameters. Check their documentation. The example i gave assumed you were using iron-router.

1 Like

I use Iron Router, but I don’t want the Id in the URL. Therefore I’d like to use either ReactiveDict or ReactiveVar, what is the difference between the two? Unlike Session, when your application reloads (on save or deploy) the values won’t be restored, what is the true significance of this?

To tie off the work flow loop here, I’d put a click event on each row, with the user_id set on that row, and set the router.parameter there, then call Router.go(‘user_messages_detail’)?

With reactivedict, the values ARE restored.

ReactiveVar is a single variable.
ReactiveDict is a dictonnary, like Session.

This is a great discussion. Thanks all for getting it started.

I’ve been mulling this over for the past couple of months, with regard to how to override/share data contexts between Iron router’s main yield and the header, footer, and aside templates; and keep coming back to Session variables. The trick, I think, is getting the name of the variable correct… activeUser rather than selectedUser communicates a session variable may persist through routes, and isn’t simply tied to a url parameter.

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.

Also, is the Session/ReactiveDict API complete enough for this use case? I’ve already implemented a session-extended-api package which has clear() or toggle(), and am now thinking a hasId() or isCurrentlySet() function would be useful as well.

Session.hasId(‘activePost’, this.params.postId)
Session.isCurrentlySet(‘activePost’, this.params.postId)

With regard to templates, mitar’s meteor-components package is the way of the future, I think. If not his package exactly, then something very similar. A ReactiveVar can be attached to it, and scoping concerns should all be addressed. If someone wants to override the ReactiveVar with a Session, they can do so.

1 Like

I’m confused, as per the reactive-dict Atosphere Forward page,

This package provide ReactiveDict, a general-purpose reactive datatype for use with tracker. It provides all of the functionality of the Session object documented in the main Meteor docs, such as reactive get, set, and equals functions, except that its contents are not saved across Hot Code Push client code updates.

The, “not saved across Hot Code Push client code updates,” said to me the values won’t be restored. Was there an update that changed this, or is the Forward wording wrong?

Session is always and forever global. ReactiveVar and ReativeDic can be local(or global per use) and wont survive hot code update.

So you’re voting for using Session over ReactiveVar/ReactiveDict for passing data between Templates? Will you elaborate on why you kept coming back to Session? Also, will you elaborate on your naming conventions so far?

I think storing the entire document will be too seductive for some; the tendency for Session scope creep would ensue…

The ReactiveDict is just a hash (key/value) pair no? What kind of API would you need besides get/set on this data structure? The rest is just sugar, am I right?

I’m not sure, based on this article by @sashko, things may change in the next version of Blaze.

Well, Session is just an alias for ReactiveDict - they’re the exact same thing - so it’s a vote for ReactiveVar/ReactiveDict.

I’ve been using selectedFoo as the naming schema for most of the persistent session variables related to routes, and I think there’s a tense issue going on which makes it confusing. Similar to how the Templates.foo.created API is migrating towards Templates.foo.createdOn. It’s the difference between present tense and past tense. Or maybe between past tense and future active tense.

I thought the same for awhile, 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.

And it’s that round-robin that I’m thinking of when I talk about extending the Session API. In practice, one will often want to check if the URL _id parameter is the same as what’s in the current Session/ReactiveDict variable. equals() and get() both sort of do half of what’s needed, but why do we have upsert() when we also have insert() and update()? It makes for a cleaner API and more concise code, is why. And part of the round robin for using Session/ReactiveDict to share data between randomly accessed templates is fetching/storing it to/from the database. That’s been my experience, anyhow.

As for Sashko’s article, I think mitar’s meteor-components package is totally compatible with it and the Blaze 2 hackpad. Might need a bit of tweaking, but he’s got the right idea, imho.

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.