Patterns and practices for passing data between templates

My previous post was more of “I feel your pain” and less of here is an/the answer. From my part, this is my rule of thumb

If its between pages/routes, use router variables/parameters
if its between a parent and child template, use property reference, namespaced session variable or local vars
if its between a non parent-child template, use centralized broker system

2 Likes

In meteor world (or in any reactive javascript client app world for that matter) pages, parent/child templates and separate templates are in fact in the same top level reactive context so why provide separate solutions?

One global reactive solution (probably a message/event broker/bus) would work for all the cases and keep everything simple.

In fact, here’s a video that describes exactly how I feel a solution should bring on the table for this kind of problem

2 Likes

@serkandurusoy I’m not a big subscriber to the one hammer for every job. If you are building a simple app, you might not need tools built for complex apps but I see your point. I think every development/case is different and knowing your options and where they make sense can help people make decisions.

3 Likes

This seems like sound advice to me.

To you, what is the difference between:

and

To me, this can be one and the same… In fact, the two Templates I need to pass data between (‘Posts’ -> ‘Post Details’) are just Templates with their own Route (in Iron Router), their own Subscriptions (two different Collections even), Helpers, and Events. The only reason they are ‘linked’ is because one page has a [userId] and the other page should take this [userId] and do a filter on a Collection with it.

I mean, what I’m really doing is a [join] between two Collections… I want to pass the [userId] to a Publish method on the Server so I don’t have to pull all Posts for all Users to the Client and do my filtering there.

Right now I’m using [onCreated] (the newest version) to do a Template level Subscription (instead of [onWait] in IR):

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

And the corresponding Publish:

Meteor.publish('user_messages_detail', function () {
    return user_messages_detail.find({});
});

I’d like to pass as little as possible to the Client and just do sorting and security trimming there. So modifying the code above, what I’d ideally like to do is pass a [userId] to the [onCreated] function:

Template.user_messages_detail.onCreated(function (_USER_ID_) { 
  var self = this;
  self.autorun(function (_USER_ID_) {
    self.subscribe('user_messages_detail', _USER_ID_);
  });
});

and this this would pass into the corresponding Publish:

Meteor.publish('user_messages_detail', function (_USER_ID_) {
    return user_messages_detail.find({_userId: _USER_ID_});
});

This would solve my problem (I think).

But how to get [USER_ID] from one Template to the next Template?


This is the ‘official’ Meteor forum, just think how valuable it would be for someone from the MDG to add commentary, provide guidance and direction on this important topic?

2 Likes

I’m of the same opinion.

Different tools (patterns) for different problems I always say… in fact as you pointed out earlier, different tools for the same problem too.

Embrace the situational.

Don’t subscribe to one ideology too harshly.

Avoid dogmatism — specially when it comes to software development.

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