MeteorFlux Flow

What I like very much about the flux architecture is the dispatcher (a.k.a. Mediator-Pattern). I first found a very good implementation of this pattern with ChaplinJS a framework for backbone.js.

I was also starting to think about implementing flux for meteor but found a pretty solid solution that was already build: space-ui

Space-UI has some more benefits e.g. having complete control over the application and no need to rely on the folder depended load order or even alphabetically one. Also the package-only structure is not needed for the whole application anymore.

What space-ui lacks are components and a router, but if you combine it with the simple flow-router and flow-components (or react.js if you like) I think you have a pretty solid application architecture based on the core concepts of flux.

4 Likes

I’ll take a look at those. Thanks @manuelschoebel!!

I’ve been taken a deep look at the space-ui but that’s the kind of overcomplicated framework I don’t want.
It’s like your are making an app with space-ui and not with Meteor anymore.

By design these frameworks can be great, but in the end, something like this is very slow to code and figuring out how something works is not straightforward because you need to follow complex chains.

For example, the chain of the clearCompleted action in their todo example goes like this:

// event is fired in '/client/views/footer/footer.coffee'
'click #clear-completed': () -> template.mediator.onClearCompletedTodos()

// then it looks like you have to write this in '/client/lib/events.coffee'
CompletedTodosCleared: {}

// then do this in '/client/views/footer/footer_mediator.coffee'
onClearCompletedTodos: -> @publish new CompletedTodosCleared()

// then in '/client/stores/todos_store.coffee'
@handle CompletedTodosCleared, on: -> @commandBus.send new ClearCompletedTodos()

// then write this in '/shared/lib/commands.coffee'
class @ClearCompletedTodos extends Space.messaging.Command
    @type 'ClearCompletedTodos'

// and finally in '/server/todos_controller.coffee' you get to delete the items
@handle ClearCompletedTodos, allowClient: true, on: (command) ->
    @todos.remove isCompleted: true

I love Meteor because everything is as simple as it can be. No boilerplate code anywhere. Everything is straightforward.

The Facebook’s Dispatcher implementation is the minimal amount of API needed to ensure you can follow the Flux principles, and all the other stuff is done with te Meteor tools we already know.

So I am going to stick with the Facebook’s implementation for now and keep working to see if I can find its boundaries.

1 Like

I’ve got to say, your implementation looks pretty darn clean to me (at a quick glance at least). So what was your reasoning for embracing flux, but not react? Obviously (as you’ve proved) they aren’t inseparable, but as they follow the same sort of ethos, I imagined I’d see them used together almost exclusively.

Meteor doesn’t impose you any architecture. That’s a good thing because you can choose the one you prefer, or don’t use any architecture at all.

Coding with an architecture is better, because you don’t have to think where to put your code each time you add something. And it saves you a lot of trouble when the application gets bigger because bugs are easy to find and everything is organised following some pattern.

Flux is a new architecture designed by Facebook for reactive apps. React is one of those. But Meteor is another one. I prefer Meteor over React because it gives you way more than just the front-end.

So I’m just trying to figure out the best way to make Meteor apps with Flux architecture, but with this singularity:

  • Make things as simple as possible, like Meteor does. This means no boilerplate code, complicated APIs or more stuff to learn.
  • Use as many Meteor tools as I can: Spacebars, Template helpers & events, Mongo Collections, Subscriptions, Sessions, Meteor methods… even IronRouter or Arunoda’s Flow Router.

I don’t want to reinvent the wheel.

By the way, any help is welcomed :smile:

Sorry I didn’t mean using Flux and React alone, I meant using the official package: https://github.com/reactjs/react-meteor

As then you’re not replacing Meteor at all, just Blaze.

Ohhhh, ok, sorry.

Like I said, I would like to make it work with the official supported tools.

Besides, I prefer Blaze over React. I think it is simpler to use and you have to write less code to do the same things. But if you prefer React, you can use it instead.

I have added another example: a simple shopping cart.


http://cartflux.meteor.com/

This time, it has two stores and some error handling (when adding a product to the catalog).

I will add another version with accounts and routing as soon as I have more free time!

@luisherranz thanks for sharing the examples. It’s really interesting indeed.
One question about your implementation compare to space:UI. How do you check the MAGIC callback strings correctness. Looks like dispatcher doesn’t care about the actual actionName, if I rename actionType only on one side of dispatcher interaction from dispatcher perspective everything still works. With mediator you at least get JS error during test run, while with current dispatcher you need to have a full functional test for every action. Which can be challenging sometimes if it’s something like a mouse drag event.

You are correct, you can use another name or not use a name at all.

That gives you less immunity against typo errors but more freedom.
For example, you can solve your concern with something like:

Dispatcher.register(function(payload){
  if (payload.actionType === undefined) {
    console.log("You forgot to set an actionType!");
}});

By the way, this is Facebook’s implementation but ported to Meteor.
You can check the original repo in:

Their API is bare-bones but powerful enough to create full Flux applications.
Once you figure out how it works, building helpers on top of that is very easy.

Facebook people use ActionCreators and ActionConstants as well, but you don’t need an API for that.
You just do:

SomeConstants.SOMETHING_HAPPENED = "SOMETHING_HAPPENED";
// and...
var somethingHappened = function(arg1, arg2){
  Dispatcher.dispatch({ 
    actionType: SomeConstants.SOMETHING_HAPPENED, 
    stuff: arg1, 
    more_stuff: arg2
})};

My goal now is not to extend the basic API, but figuring out the best way to use Flux within Meteor :blush:

signals solves the magic string problem
http://millermedeiros.github.io/js-signals/
I really like it, I’m using it in a Meteor app.

However, there is one downside with Signals, and most event based frameworks which is that “events” “signals” , “actions” , (whatever you want to call the message object), these are sent via DOM events. So, if you want to send or receive messages from the perspective of a particular object, it must be on the DOM.

Is this true for this Flux Meteor port ?

One way around this is to create a single DOM object, add it to the document body. Then all objects ( whether on the dom or not ) can use this single DOM object to relay all messages.

Ideally, providing the reference to this single DOM message relay object can be done automatically. Otherwise it’s a lot of boilerplate for every view that needs to tap into the messaging system.

thanks,
dave

I have updated the shopping cart with materializecss (it’s great, by the way):
http://cartflux.meteor.com/

I added a modal for the Add Product form. Click on the blue googlish button to see it.

I am controlling it with actions: “OPEN_ADD_PRODUCT” and “CLOSE_ADD_PRODUCT”.
The store receives those actions and saves the state in a Session variable called “Catalog.OpenAddProduct”.
Then the View (AddProductView) react to that Session variable to open or close the modal.

The Store can closed it in other situations, for example, when the action “ADD_PRODUCT” adds a product successfully. It just sets “Catalog.OpenAddProduct” to false.

I am not sure if that’s the best way to deal with this kind of things. In a way, the Store is modifying the App State, which is great (and survives hot code pushes) but in other way, the Store is too responsible about what the View should or shouldn’t show, which doesn’t seem good.

Any thoughts?

1 Like

I’ve solved this issue changing the names of the actions and the Session variable.

Now they are "USER_IS_ADDING_PRODUCT", "USER_IS_NOT_ADDING_PRODUCT" and Session.get("Catalog.userIsAddingProduct").

That way, the Stores doesn’t know if there is a modal in the view or not, it just knows if the user is currently adding a product.

The view can do whatever it needs to render a form to add a new product, not matter if it’s an in-page form, a modal or some other thing.

If the user added a product successfully, the Store knows he is not adding a product anymore, and therefore it sets the Catalog.userIsAddingProduct to false.

Then, the views act accordingly and reset or remove the forms and the errors, close the modal and so on.

So in the end, the view is exposed to data (like the products of the Catalog collection) and app state (like the Session variable Catalog.userIsAddingProduct).

This is a good practice I think. For example, I like that translate.google.com writes all the state in the url so you can easily copy and send it:
https://translate.google.com/#en/es/All%20the%20state%20is%20saved%20on%20the%20url%20so%20I%20can%20copy%20and%20send%20it

Next step: Publications!

I’m really interested in where this is going, keep it up!

1 Like

I have added publications:
http://cartflux.meteor.com/

I don’t know how this would work once I have added a router, but without one yet, it is quite easy.
The Views trigger actions and the Stores (which control the subscription) decide to change it or not.

For example, to change the subscription based on a search query, something as simple as this works well:

Template.NavBarView.events({
  'keyup #search': function(event,template){
    Dispatcher.dispatch({
      actionType: "USER_HAS_SEARCHED_PRODUCTS",
      search: event.target.value
    });
  }
});

And in the Store:

 // CatalogStore Subscriptions
 if (Meteor.isClient){
   Tracker.autorun(function (){
     Meteor.subscribe('Catalog.catalogSearch', 
         Session.get("Catalog.searchProductsQuery"));
   });
 }

 // CatalogStore Publications
 if (Meteor.isServer) {
   Meteor.publish('Catalog.catalogSearch', function(search) {
     var regexp = new RegExp(search, 'i');
     return Catalog.find({name: regexp});
   });
 }

 // Method triggered by "USER_HAS_SEARCHED_PRODUCTS"
CatalogStore.searchProducts: function(search){
  Session.set("Catalog.searchProductsQuery", search);
}

Next step: The Router! (First IronRouter, then FlowRouter).

EDIT: Ideally, your Store would be smart enough to add to the collections only what the Views need to show, like in this great tutorial by Pieter Soudan http://meteorpatterns.com/Sewdn/query-collections

2 Likes

Thanks! Feel free to share your thoughts about all this : )

I’ve been studying publications and subscriptions a bit more.

It looks like subscriptions are smarter when they are put inside a Tracker.autorun.

If you do something like this:

var catalogSub = Meteor.subscribe('Catalog.catalogSearch', "");

var searchProducts = function(search){
  catalogSub.stop();
  catalogSub = Meteor.subscribe('Catalog.catalogSearch', search);
};

All the data is thrown away and then sent again.

But if you do something like this:

Tracker.autorun(function(){
  Meteor.subscribe("Catalog.catalogSearch",Session.get("Catalog.searchQuery"));
});

var searchProducts = function(search){
  Session.set("Catalog.searchQuery", search);
};

Meteor diffs the data and sends only what is necessary.
It seems like there is no way to do that without Tracker and a reactive variable.


In the new Meteor 1.0.4 you can set subscriptions in Templates and Meteor creates/destroys them automatically when the Template is created/destroyed. It gives you a helper called Template.subscriptionsReady as well.

I’ve been thinking if it is ok to move subscriptions from Stores to Views in a Flux app.

If Views (Template.helpers) are allowed to decide what to retrieve from Minimongo:

Template.SomeView.helpers({
  some_helper: function(){
    return SomeCollection.find({name: "something"});
  }
});

They should be allowed to decide what to retrieve from MongoDB, shouldn’t they?

Template.SomeView.onCreated(function () {
  var self = this;
  self.autorun(function () {
    self.subscribe("someSubscription", Session.get("somethingReactive"));
  });
});

I am not 100% sure yet, but I think it may work.

Views are still not allowed to insert/update data in Collections or set Session variables. If a View wants to modify a subscription it has to send an Action and a Store has to change the App State (Session or Collection which affects the subscription). So App State is still managed in Stores.

I would prefer if you don’t have to include a reactive variable in the subscription but it looks there’s no way around it.

I have refactored and updated the repo and the demo:
http://cartflux.meteor.com

By the way, before routers, I am going to:

  • Remove insecure package
  • Include user accounts
  • Implement pagination

At this point, I wanted to try “the Flux way” of retrieving data.

What I’ve been calling “App State” in Meteor (Collections+Sessions) doesn’t exist in Flux because it only provides a dispatcher and a frontend (React). So, when the frontend needs data, it asks the Stores, not the “App State”.

I have refactored the shopping cart app to try this. You can check the new code here:


http://cartflux.meteor.com

Now, when a View needs data, it calls a Store method like:

Template.CatalogView.helpers({
  catalog_products: function(){
    return CatalogStore.getSearchedProducts();
  }
});

It doesn’t access the Collection directly anymore.
As you can see, the logic about what is a “SearchedProduct” is now on the Store and not on the View.

Besides, I added the reactive-var package so now every reactive var is local to the Store.

var adding_product = new ReactiveVar(false);

Stores expose them with methods, like:

getUserIsAddingProduct: function(){
  return adding_product.get();
}

And Views retrieve them with:

CatalogStore.getUserIsAddingProduct();

Pros:

  • Stores now control how to update the data and what to expose.
  • No more confusing “global” Session variables.
  • All the data is retrieved with getter methods, no matter if it comes from Collections or Sessions.
  • Views are less coupled to the model.
  • Views need less logic when retrieving data.
  • It’s easier to add unit tests.

Cons:

  • You have to write slightly more code.

My conclusion:
I thought it was going to be a lot more code, but it’s not that much, really.
This way the Flux pattern is easier to understand and the app flow is easier to follow. I like it.

Any thoughts?

2 Likes

Very interesting. Keep it up.

1 Like

Awesome posts. Would be great to see this as serious of blog articles.
One question I have is how do you deal with latency compensation when using the dispatcher. Does it still work ?

1 Like