Inter component/template communication (architecture)


#1

There are lot of component system trying to solve encapsulation of UI logic for reusability, but they don’t seem to solve the inter component communication issue. How do/should components/template-instance talk to each other to perform unit actions.

Eg. If I have a edit post dialog component that can be triggered from multiple places/components in an app,
How do these different/multiple components, communicate with an editPostDialog component to perform edits? The editPostDialog unit task is to take a post, provide ability to user to edit and communicate back to the initiating component with the edited post.

It communication with parent-child component seems to be fairly easy using referenced properties.

How is the meteor community solving non parent-child (per my editPostDialog example) component/template-instance communication?

For my part, I started looking into centralized dispatcher/emitter to handle inter component communication which gets setup on template onCreate but that falls flate very quickly when multiple component subscript to the same events.


How to call functions between templates?
Patterns and practices for passing data between templates
Patterns and practices for passing data between templates
#2

You can use a reactive var or reactive dict based approach. If what you’ll be tracking is very template/component specific, you can keep it scoped to the component’s creation.

Or, as you have already decided, you can create a central “place” to hold state. Don’t think of it like a dispatcher, but think of it as a state broker which takes in input from all the resources (initiators) in the app and update a dictionary of state objects where any other resource as well as the initiatior can react to the changes of.

Tracker, actually, is a great library to accomplish just that.


#3

@serkandurusoy Lets take my example above and apply a state management solution per your comment. Lets assume that we have two components postDetail(shows the full details of a post) and postTile (shows just a tile version of a post). Each of these component/template need the functionality to allow the user to edit a post i.e. talk to the editPostDialog to perform this action.

lets provide a state for edit

var editPost = new ReactiveVar(null);

now in order for editPostDialog to react to that state, we will have to add the following code

this.autorun(function(){
 this.post = editPost.get()
}

now when edit is complete, editPostDialog has to send the edited post back to its initiator so they can do whatever they need to do with the new temporary state. To accomplish that we need to add another state. …See where this is going.

var editPostDone = new ReactiveVar(null);

Which both postDetail & postTile component/template will have to react to by adding the following code

this.autorun(function(){
 this.postedit = editPostDone.get()
}

Now, since both postDetail & postTile depend on the ReactiveVar editPostDone, editing from postDetails will reactively run the above auto run in postTile as well. Sure, we distinguish between the two by matching their post id with the post id from the editPostDone state but thats just one case. They are many inter component/template communication that might have a mechanism to distinguish between which component should be affected by state change.

Applying the same example to say Polymer,

When editing from either postDetail & postTile, the code will look like …

//on edit
self.on_edit_done = function(event/*with data*/){
  //do some work here
 .....
 //release handler
  editPostDialog.off('edit-done, self.on_edit_done);
}

var editPostDialog = document.find('editPostDialog');
editPostDialog.post = postToEdit
editPostDialog.on('edit-done',self.on_edit_done)
editPostDialog.show();

Looking on the code above, polymer allow you to interact between the components just like you would between any html control. Direct subscription and un-subscription to events allows us to detach from the control when we have no use for it any more.

I think using Tracker will be possible if its has the ability and easy to start and stop reactivity on a particular state at will. I also don’t like using external states within components for that goes against encapsulation nature of components.


#4

For the specific case of dialogs, I make them children of the issuing template.


#5

@Steve That solve the problem but you end up with dialogs everywhere. I think its much better to use a single instance for performance and ease of code management.


#6

To accomplish that we need to add another state. …See where this is going.

Why do you need multiple states?

You can use a reactive dictionary instead of a reactive variable. That would be to keep a common context.

Then, your states could beobject literals that contain:

  • name of the state tracker
  • value of the state tracker
  • “a return back to the initiator indicator” (I don’t know what to name this, but please read below)

All initiators then check two things:

  • vaue of the state tracker for the one it wants to depend on
  • the “a return back to the initiator indicator”

It can then decide to do depending for example on the value being the same as its own “name/id”. If it is the same, it acts on it, otherwise not. Or, depending on your component configuration, it acts on it regardless of that value.

Object literals won’t introduce overhead to your app memory utilization, keep the number of tracked values sanely manageable and are also extensible.


#7

@serkandurusoy Would be great if you can elaborate on your last post with code sample. It wasn’t clear to me as to your description of using reactive dictionary. Can you provide a sudo code sample on the editPostDialog scenario.


#8

Sure. Here’s a semi-pseudo take on what I had in mind:

Initialize broker

Broker = new ReactiveDict;

Initiate action

/* click a button etc */
Broker.set('editPost', { requestedBy: 'buttonComponent', requests: 'postContent', requestTo: 'buttonComponentReturnTracker' });
Tracker.autorun(function(){
  if (Session.get('buttonComponentReturnTracker')) {
    /* do something and clear session */
  }
})

Handle action

/* show post edit form on a modal and do editing*/
brokerage = Broker.get('editPost')
result = formObject[brokerage.requests];
if (brokerage.requestsTo)  {
  Session.set(brokerage.requestsTo, result)
} else {
  /* do what you'd normally do */
}

I’m sure this code lacks a lot of things but if the general idea looks interesting to you, we can explore it further and perhaps factor out a one-liner function out of it so that it can be applied easily.


#9

@serkandurusoy got it. Its still a dispatcher pattern but the dispatcher holds that state instead of a passing it through. I think this will work. We wont have the ability to stop and start reactivity on the state but …we can live with that. Its seems weird to use Session and a reactive dictionary for they are almost the same, except for persistence after hot code push.

If we want to maintain state between hot code push, we can wrap Session with a centralized Broker instance.


#10

Yep, that’s right. It is not very clean, but I suspect it would work for basic scenarios.

About dict, yes it is essentially session with a custom name. I like it because it works like a context where var is all over the place.

Reactivity can be stopped, though. Tracker’s handle does provide a non-reactive option so that can also be a parameter of the dispatcher.


#11

@emmanuelbuah you might want to check out the following thread for a similar discussion.


#12

@serkandurusoy thanks for sharing.