christianalfoni / flux-angular

Use the FLUX architecture with Angular JS
313 stars 50 forks source link

Best way to use mixins #1

Closed gilbox closed 9 years ago

gilbox commented 10 years ago

Christian, thanks for creating this project and your accompanying blog entries, very cool stuff. In the README file you have the following tip:

You do not want to divide your stores within one section of your application as they very quickly become dependant on each other. That can result in circular dependency problems. Use mixins instead and create big stores. mixins, actions and handlers will be merged with the main store.

Can you expand on this idea? Do you suggest having just one big store (ie AppStore) and a lot of mixins?

christianalfoni commented 10 years ago

Hi @gilbox and thanks so much for your feedback! Let me explain what I mean and if it resonates with you I will update the README with it too :-)

Though I have experience building flux applications, I still lack the experience using the concepts on many different types of projects. I have of course given it a lot of thought so let me share those thoughts with you.

On JSFridge there are basically two sections:

  1. Front page which has search, lists courses etc.
  2. The editor itself

These two sections have their own respective stores as they would never be dependent on each other. I also chose to have a "UserStore" which needs to be available to any page, to check if the user is logged in etc. So based on that experience there are two types of stores:

  1. General state that covers any content in your application. This would be your typical user store. You probably need to know at any time if the user is logged in or not
  2. Content specific stores. These are stores handling a specific section of your application. This is typically different pages

But you mention creating one "AppStore" and I think you are spot on with that one. When starting out with a new project you have no idea how all the state of your application needs to be implemented and shared. So always starting out with one single store I believe to be the best strategy as you will not have to worry about implementing new state and the consequences of doing so . As you develop and get to know your state and how it is shared across the application you can consider splitting your "AppStore" into multiple stores to separate concerns.

Thanks again Gil, and I hope this was of help!

Btw, you just made my day. You are the first person to use the record functionality on JSFridge :-) I am still working on the project and I am soon going into the "usability and design" phase. Please share any thoughts you have as I am trying really hard to figure out how to use the technology in the best possible way.

gilbox commented 10 years ago

Haha I didn't realize I was the first to use JSFridge record feature. That's really cool because I am totally blown away by JSFridge. It seems like a wonderful teaching tool. I watched a couple of your recordings and it was such an intuitive way to learn.

Your explanation re: starting with one store makes perfect sense, thank you so much for elaborating!

If you don't mind, I have one more question about your decision to not include any concept of Action Creators. I've been trying to wrap my head around the best way to deal with asynchronous data. Facebook seems to recommend making async calls in the Action Creators. In the Fluxxor implementation that looks something like this:

var actions = {
  loadBuzz: function() {
    this.dispatch(constants.LOAD_BUZZ);

    BuzzwordClient.load(function(words) {
      this.dispatch(constants.LOAD_BUZZ_SUCCESS, {words: words});
    }.bind(this), function(error) {
      this.dispatch(constants.LOAD_BUZZ_FAIL, {error: error});
    }.bind(this));
  },

  addBuzz: function(word) {
    var id = _.uniqueId();
    this.dispatch(constants.ADD_BUZZ, {id: id, word: word});

    BuzzwordClient.submit(word, function() {
      this.dispatch(constants.ADD_BUZZ_SUCCESS, {id: id});
    }.bind(this), function(error) {
      this.dispatch(constants.ADD_BUZZ_FAIL, {id: id, error: error});
    }.bind(this));
  }
};

I guess you must be dealing with async processes in the Stores themselves? Has your experience been different than those recommending doing this stuff in the Action Creators?

christianalfoni commented 10 years ago

Hi again @gilbox :-)

Thanks so much for the feedback on JSFridge, I am really hoping it will be a valuable tool for a lot of developers, even people who wants to become a developer. Working on more content now and getting a new design in there soon. Aiming for launch just over new years.

But lets talk about your question. I run all my async operations inside the stores. It is an interesting concept to run async operations in the action creator, but to my experience there has not been any challenges running async stuff in the stores. Regarding testing I do not really see an issue because you would mock BuzzwordClient when testing the store, though generally async testing is harder than sync testing.

As I understand "cascading dispatches" it is about not causing actions to trigger new actions, as a principle in FLUX. You can not really avoid that by doing some things in action creators and some things in stores. You just have to keep it fluxy with action -> store -> update -> component, not cause a action -> store -> action -> store -> update -> component type of situation.

As I understand "prevent multiple dispatches" it has to do with keeping stores in sync when a dispatch that goes to two stores causes an async operation in one of them, and sync in another. You do not want a new dispatch to trigger while one of the stores is working. That is not an issue in flux-angular because you use mixins instead. Two stores that depend on each other should not be two stores, they should be one, using a mixin :-)

Let me give you an example on JSFridge :-D You can actually set up a simple CRUD API on your fiddles, so this one has that, http://www.jsfridge.com/fiddles/1415467678288. Doing some async stuff. Let me know what you think, especially if you see any issues compared to the examples you provide here.

gilbox commented 10 years ago

The JSFridge CRUD feature is super-nice. Good to know about it. Looking at your example I really find myself wishing i could fork it and mess around with it, but I'm sure that's one of a million features you're considering adding.

I think I see what you mean about the Store mixin concept solving some problems related to keeping the flow unidirectional.

One thing that seems radically different compared to the Action Creator approach is how to allow optimistic updates. Building off your example, I'm imagining something like this:

      addMessage: function (text) {
        var message = {text: text};
        var messagesCopy = angular.copy(this.messages);
        this.messages.push(message);
        this.emitChange();
        $http.post('/API/messages', message)
        .success(function (savedMessage) {
          this.serverUpdate = 'Added message: ' + message.text;
          message.id = savedMessage.id;
            this.emitChange();
        }.bind(this))
        .error(function() {
           this.messages = messagesCopy;
           this.emitChange();
        }.bind(this));
      },

I don't have enough experience with Flux yet to determine which approach is better for what circumstances, but I hope to figure that out as I mess with it more. Thanks so much for your thorough explanations.

christianalfoni commented 10 years ago

hi again :-)

Actually optimistic updates works the same way. As you see in Fluxxor example the action creator triggers an "add_buzz" action with an ID that makes the store put in a new buzz. This is what we do at the top of our function here too. When the success of the API arrives it triggers a new action that uses the same ID to set an "OK" on the buzz. In the example we do not need an ID to reference our message as we have access to it in our scope, but we do take the ID of the message returned from the server and update our message, which is a very common scenario.

It is a bit weird to use that buzz thing as an example, because commonly you work with creating entities on the server and you will need the server produced ID back to update your "optimistic model". They would have to work with two sets of IDs it seems. One for action creator and store to reference the correct model and one that actually represents the server side ID. Hm hm, but I should be careful about stating anything on fluxxor. Do not know it very well.

Your code looks good, I would just make one small adjustment to make a bit faster:

      addMessage: function (text) {
        var message = {text: text};
        this.messages.push(message);
        this.emitChange();
        $http.post('/API/messages', message)
          .success(function (savedMessage) {
            this.serverUpdate = 'Added message: ' + message.text;
            message.id = savedMessage.id;
            this.emitChange();
        }.bind(this))
        .error(function() {
           this.messages.splice(this.messages.indexOf(message), 1);
           this.emitChange();
        }.bind(this));
      },

Since we have access to our message in the scope we can just remove it directly from our messages array :-)

FLUX is still very young and there will be a lot more discussions, libs and frameworks popping up. I am a big fan of keeping the syntax as clean and simple as possible, which is today a big challenge with React JS and especially the dispatcher and store concepts. Very verbose stuff. But things will only get better and FLUX as a concept is awesome :-)

You know what, I will try to get that forking in tomorrow. It should not take long and it is absolutely needed! Thanks for pointing it out! And please, ask my anything, I will try to answer at the best of my ability.