Patterns and practices for passing data between templates

I’m curious what’s wrong with using Session.

It’s a global---------------

Collections are global too. If I define this:

Posts = new Mongo.Collection('posts');

Then Posts is accessible everywhere. It’s global.

Yes, collections are usually globals. The important part is “usually”. You can limit their scope if you want to, you can have a Posts variable refering to a collection in a part of your app, and refering to another collection (of whatever you want) in another part.
The problem with Session is that you cannot do that. Every part of your app has access and can modify every Session variables, even when that’s not intended.
Limiting the use of global variables is a base rule of programming.

I don’t think this is too valuable for this discussion, but to explain:

I’m not actually building a Forum system, I just used it as example to make my problem easier to explain.

I’m actually building a messaging/communications system.

The Posts screen I referred to will just be a list of all the users in the system. When you select that person from the list, you’d go to a Posts Detail screen, which would actually be all the Posts, or in my case Messages, the User has ever sent back-n-forth with the Client.

Or put another way, in my case the Posts are not topic based, but User based.

@aadams I get where you are coming from. I have a post similar to yours here Inter component/template communication (architecture)

The reason this question keeps coming up is that templates and components solve most pain point but fall short with in the communication department. Can you imagine html controls (buttons, inputs etc) communicate through session variables? I cant. Communication between html controls is achieved through control properties and events, making each control standalone at the same time, providing a decoupled communication channel. You can set properties, subscribe or unsubscribe to events to cause or react to state changes. Polymer stays with this concepts and its much easy to do inter component/template instance communication.

I also think a centralized communication broker (emitters, etc) is a good options if its not a parent-child relation since it takes decoupling a step further (controls don’t reference each other). If its a parent-child relation, you can use referenced properties. Exploring and building a good central broker is needed by the meteor community, I think. There are a few like https://atmospherejs.com/raix/eventemitter (using it for my current broker implementation). Pure event brokers have their limitations. React proposes a similar mechanism for non parent-child component communication - http://facebook.github.io/react/tips/communicate-between-components.html

@mitar release a great blaze-component - Announcing Blaze Components - Reusable components for Blaze and I plan on expose blaze-component as web-components so we can communicate between components using the good old control properties and events, just like html. We could also do the same for blaze.

2 Likes

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.