Comments (14)
Hi pramodsankarl,
this is a design choice of the flux architecture. While you are in a dispatch progress you cannot dispatch another action because you will get pretty soon in an infinite dispatch loop.
So make it run step by step. You can combine your user set and user get in one action like setAndGetUser() or something. The controller or directive should not be responsible for the order of dispatching. Just react with a action call on userInput or and set data on store events.
Hope this helps. Have fun!
My personal pattern is something like that:
function MyController($scope, MyActions, MyStore, MyStoreEvents){
registerStoreListeners(); //registre store event listeners
initData(); //init Data the first time
initalActionCalls();
function registerStoreListeners(){
$scope.$listenTo(MyStore, MyStoreEvents.dataUpdated,initData); /*look here we use initData again*/
// this is the place for more event listeners.
}
function initData(){
this.myData = MyStore.getData();
}
function initalActionCalls(){
MyActions.someActionToDispatchOnControllerStart();
};
}
from flux-angular.
@SuperheroicCoding Thanks a lot for your quick reply and appreciate your suggestion. I think i had an over simplified example by combining both to the user You can combine your user set and user get in one action like
. But what if when user is set i am going to fetch some other entity (say Account entity related to the user which cannot be a part of userActionCreator or userStore) related to the user from within another component using another actionCreator and its respective entity store listening to that dispatch action. i understand the dispatch within dispatch issue and why it happens.. Even here in my case, components (directive/controller) are listening to the events and acting on it and they are not really controlling the order as well. Or am i missing some thing in the design... Apologies if i confused with my example.
from flux-angular.
Hi,
I understand because in my current project I had the same quite difficult problem. loading a user then loading the users children and then loading the children events. With different actions different stores and multiple controllers listening to events. maybe there is a better way but i tried to return a promise in my async action and then i could chain the calls in another action class. but that felt not very clean.
Talking about that an idea comes up. maybe it is possible to resolve a promise inside the listener. something like:
var deferred = $q.defer();
deferred.promise.then(anotherDispatch);
$scope.listenTo(MyStore,function() {deferred.resolve();
});
But I realy don't know if this is working without leading to the same dispatch while dispatching error. I will test it soon. Let me know if it helped.I am realy curious about solutions to this common problem.
from flux-angular.
@pramodsankarl @SuperheroicCoding hi, I was reading the code for flux-angular and looking at the dispatch method. I saw that it says "or queues it up if one is already in progress" in the comments, but I could not find any code that does that?
In regards to the design pattern, I don't think it's a bad design that you may need to update other components that you're going to the server to save/update some data that they may depend upon? That could give them a chance to put up a spinner and safe guard against user interaction while you are doing some saving?
/**
* Dispatches a new action or queues it up if one is already in progress
* @method dispatch
* @param {String} actionName Name of the action to be dispatched
* @param {Object} payload Parameters to describe the action
* @throws {Error} if store has handler registered that does not exist
*/
Dispatcher.prototype.dispatch = function dispatch(actionName, payload) {
if (!actionName) {
throw new Error('actionName parameter `' + actionName + '` is invalid.');
}
if (this.currentAction) {
throw new Error('Cannot call dispatch while another dispatch is executing. Attempted to execute \'' + actionName + '\' but \'' + this.currentAction.name + '\' is already executing.');
}
from flux-angular.
@mjw56 I agree with you, as i could not find anything wrong in this kind of design (I could well be wrong.. :)) as well. In this particular situation it may be loading up the spinner, but with the complexity of an application having many components there could be situations where one component making a dispatch synchronously within the handler? But from your comment (Nice catch :) ) it seems like there was supposed to be a queuing mechanism within the dispatcher?
@SuperheroicCoding I believe using a promise and resolving them from handlers to take some action would probably become an overkill?, so why not solve it at the top level. You could as well push the dispatch within the async queue (with a timeout or a fake promise etc..) decorating the flux service (a quick and dirty solution) specifically for a particular application. But i am not sure about the evaluation order of async queue and how is it implemented in different browsers. Just to be safe i would rather use a queuing mechanism. Plnkr. Also worth looking at this async dispatcher/flux implementation from this user which maintains a queuing mechanism.
app.config(['$provide', function($provide){
$provide.decorator('flux', ['$delegate', '$timeout', '$q' , function($delegate, $timeout, $q){
var origDispatch = $delegate.dispatch,
_async = $q.when();
$delegate.dispatch = function(){
var args = arguments;
//Or even add a check for this.currentAction to see if something is running already
//use timeout or even run it async within promise resolution.
/*$timeout(function(){
origDispatch.apply($delegate, args);
});*/
_async.then(function(){
origDispatch.apply($delegate, args);
});
}
return $delegate;
}]);
}]);
from flux-angular.
@pramodsankarl, @mjw56 big thanks for your answer.
Yes, maybe this idea of resolving promises inside handlers it's an overkill. A solution inside flux would be way more comfortable.
Either grouping all your action calls inside action dispatcher and you keep your actions dry by returning promises and reusing and ordering them in other actions. Than you are in control of your dispatch order but you must implement a lot of actions.
Or you let flux sync your calls. Seems a lot easier but the order of action dispatching is not obvious anymore. And if you must debug because you dispatched actions in a circle you are in a big mess.
The second approach is very comfortable to use but the user must be aware that it's really easy to code some dispatch circles.
What do you think?
Very interesting approach from @sterpe. I'm curious if he implemented circle detection.
from flux-angular.
Hi guys,
This is a really awesome discussion. Goes to the heart of advanced state handling. If I understand you correctly this relates to the problem where you have for example a list of projects where each project references an author by id. When you get the list of projects to your listener you need to dispatch new actions to fetch the authors of the projects... which does not work.
I think the core problem here is giving responsibilities. First of all I think it is interesting to notice that during the last FLUX-conf Q&A Jing Chen from the Flux team says that they handle "missing data" in their getters.
Initially you would think getters are just there to retrieve state from the store, but they are actually a very good hook to compose state and trigger behaviour related to missing data.
A small example:
angular.module('app', ['flux'])
.store('MyStore', function () {
return {
projects: {},
users: {},
projectsList: [], // List with project ids
exports: {
get projectsList() {
return this.projectsList.map(function (id) {
// Since we are mutating the project with a new "author" property we
// map the exact properties we want to expose to not mutate the project
// object inside the store
var project = this.projects[id];
return {
id: id,
title: project.title,
author: this.users[project.authorId]
};
}, this);
}
}
};
});
So what if we did something like this:
angular.module('app', ['flux'])
.store('MyStore', function (loaders) {
return {
projects: {},
users: {},
projectsList: [], // List with project ids
exports: {
get projectsList() {
return this.projectsList.map(function (id) {
var project = this.projects[id];
// If no project we load it and return a temporary project object
if (!project) {
return loaders.project(id);
}
return {
id: id,
title: project.title,
author: this.users[project.authorId]
};
}, this);
}
}
};
});
Okay, so what I like with this approach is:
- You can compose state in the getter. Bringing together different state you have in the store and other stores
- You do not have to trigger an action to start getting data, you get it as you need it. By "getting" projectsList you will automatically start fetching whatever you need
So what is this loaders.project(id)
?
angular.module('app', ['flux'])
.factory('loaders', function (actions) {
return {
project: function (id) {
var project = {
id: id,
$isLoading: true
};
$http.get('/projects/' + id)
.success(actions.setProject)
.error(actions.setProjectError);
return project;
}
};
});
The loader creates a temporary object the "getter" can return. It attaches a special $isLoading
prop which can be used by a component/template to check the state of the data of the project, maybe display a loader while it is being fetched from the server. $
is just a convention I use for "server data state". You can also use: "$error", "$noAccess", "$notFound" etc. The point is that you do not run actions synchronously from loaders, you return temporary objects to the "getter" that is composing some state for your components. You run actions when actual new data from the server has arrived and needs to be stored.
When the project is fetched you run an action to set the project in the store, which updates the store and any component listening to changes will now run the "getter" again where the project now exists.
I have written about this in a bit more detail, though with Baobab and React. It is the same principle, where "facets" are just like "getters" in a store: http://christianalfoni.com/articles/2015_04_26_Handling-complex-state-with-Baobab#bring-state-handling-to-a-whole-new-level
The beauty of this approach is that you can safely grab relational data from the server and "getters" map these together fetching missing data automatically. By using the $
convention you can easily check the state of the data fetching in your components/templates.
Hope this contributed to the discussion, happy to discuss it further :-)
from flux-angular.
Actually, I am considering writing up an article on this stuff using flux-angular. How to store data with "single source of truth" principle, compose state and fetch state automatically from the server. Would be great to get some feedback on the approach mentioned before diving into it.
from flux-angular.
Hey @christianalfoni,
First, thanks for flux-angular
. :)
I read your response, and I think that can work for fetching data but I still have one question. Should this approach be used for all types of UI interactions? What about the scenario where user clicks a button to update a user profile, would you call a getter on the store then too? Can you call updateUser
on the store and pass that data which will then go make the XHR call?
I came up with a slightly different approach for when updating data from the UI,
Basically, say a user clicks a button to update and save a user profile. My idea was that you would call an action 'saveUserDataClick'
from the UI which could then only dispatch one action saying 'SAVE_USER_DATA_CLICKED'
. Then, the UserStore
would hear this action dispatched and first emit an event out saying user_data_saving
which any component could listen for to put up it's own spinner. Then, I read this comment on this SO Post where Bill Fisher says that stores can make XHR requests (which you also talked about above). So, when the UserStore hears about the 'SAVE_USER_DATA_CLICKED'
action, it can also then either make the XHR call directly in the store or trigger an action updateUserData
which will make the XHR call and can then either dispatch 'USER_DATA_UPDATED'
or USER_DATA_UPDATE_FAILED'
. I know this seems kind of crazy, but it does allow for multiple components to hear about a component saving without having to do an async hack or run into the infamous cannot dispatch in a dispatch
error.
This way, you can have a log of the actions that took place: a user click to update data, and the response of that UI event from the server. Also, multiple components can then know that a separate component went and did some work on the server.
On a related note, I really like these slides from Bill Fisher's flux talk at Fluent. There is a slide in that deck about actions and newspapers. In it he says "Actions should be like newspapers, reporting something that has happened in the world. For example, the user clicked, the server responded, or the browser completed an animation frame."
Also, I think writing a blog post on this is a great idea. I don't see enough people talking about these specific types of issues and it would be really cool to see that coming from a react/angular angle.
Cheers
from flux-angular.
Hi @christianalfoni , Thanks a lot for this awesome angular wrapper library and taking time to provide a response to this thread. I like the idea of providing a state in the data set via a special property (Currently i have been providing utility functions on the store hasError, hasData to go with getError, getData for the consumers to avoid any comparison on the ui and quickly check on the state of the data in the Store).
I have a question (probably a bit inline with @mjw56 's comment) on how you manage the relationship that a related entity has with this one. Say (hypothetical example of 2 different but related Entities whose info is being displayed in totally isolated components and their respective stores1) you have Project Component and a project store which displays the details of a specific loaded project. On the same view you have another component which basically displays the details of its Contributors like their popular articles, projects, blog posts etc.. and that data comes from Contributors Store. Now you select another project (Which may or may not have the same set of contributors) So when the project component is fetching/waiting for its data, it shows a spinner via the state we provide from the temp data from the store. All is fine but at this time since there has been neither an action dispatched (already understood that this is a bad idea) nor an event emitted from the Project store2 (which Contributor component can listen to and do a get from its store) while fetching is being done in the project component, the Contributors component will be still displaying the stale data and no loading state yet(Since it does not know that project details are being fetched at this time). How do you manage this kind of related entities in completely different stores and keep the store and UI state consistent?
1Just taking Project/Contributor as example here but i actually mean A/B, cannot really combine them together and they must be in its own isolated stores. There could be more related and complex dependency chain as well like A --> B, C, D and B --> E --> F
2Say something like Project_Loading
All the best with the article, i'd be more than interested to read your opinion :)
from flux-angular.
I probably don't want to keep this as an "issue" anymore cos, i later realized that this has nothing to do with the implementation of the flux-angular library and it is more of a design dilemma.
from flux-angular.
@mjw56 : Interesting approach to using actions, though just passing an action through resulting in an event is not really a state change, it is just an event. So passing an action to a store to just emit an event seems a bit overkill. Could just trigger the event on some "event hub"? That said, I think the "isSaving" is actually a state you want to keep in a store.
So to update a user I would not use a getter, but an ActionCreator because it is explicit, unlike a getter where the loading is implicit.
.factory('ActionCreator', function (flux, $http) {
return {
updateUser: function (id, data) {
var storeData = angular.extend({}, data, {
$isLoading: true
});
flux.dispatch('UPDATE_USER', id, storeData);
$http.put('/users/' + id, data)
.success(function () {
flux.dispatch('UPDATE_USER', id, {
$isLoading: false
});
})
.error(function (error) {
flux.dispatch('UPDATE_USER', id, {
$isLoading: false,
$error: error
});
})
}
};
}
This also makes you able to dehydrate/hydrate the stores and display the UI correctly. Tracking actions is a very interesting concept though, but should maybe be an addition rather than a replacement for setting UI state? Anyways, great input :-)
@pramodsankarl You make an excellent point here! This is hard stuff to handle. But in this case I would say that showing contributors is a depending on the project and that I would listen to the project in both components, but only show project specific details in Project Component and contributors in Contributor Component. This is how the getter could work:
angular.module('app', ['flux'])
.store('ProjectStore', function (loaders, ContributorStore) {
return {
projects: {},
currentProject: null, // ID of current project
handlers: {
'updateContributor': 'update',
'removeContributor': 'update',
'setCurrentProject': 'setProject'
},
update: function () {
this.waitFor('ContributorStore', function () {
this.emitChange();
});
},
setProject: function (id) {
this.currentProject = id;
this.emitChange();
},
exports: {
get currentProject() {
var project = this.projects[id];
// If no project we load it and return a temporary project object
if (!project) {
return loaders.project(id);
}
return {
id: id,
title: project.title,
contributors: project.contributorIds.map(function (id) {
return ContributorStore.getAll()[id] || loaders.contributor(id);
})
};
}
}
};
});
So both components will use currentProject
, but they will extract different values based on what to display, but both has access to everything, including the loading state of the data.
Did that make sense?
from flux-angular.
@christianalfoni Thanks again for the response. I think that makes sense, in fact in my original example(plunker) i am doing the same (firing a dispatch via setCurrentUser
) and will definitely work. I will explore more with complex scenario. I understand that basic idea of having the store making the ajax call (via loader aka action creator) to manage its own state (to keep it in syc) rather than the action creator call (which makes the ajax call) being initiated by the UI component directly can help manage the state better. I am going to give this a try and see how it goes.. Once again appreciate the comments & pointers from you and others who were involved in the chat. Great discussion, guys!! Cheers!!
from flux-angular.
Great! Looking forward to your report on how it worked out :-)
from flux-angular.
Related Issues (20)
- example: MyStore is not defined HOT 2
- Cannot call dispatch while another dispatch is executing HOT 2
- flux-angular IE8 problems HOT 2
- immutable-store deprecated, other options? HOT 8
- Export "flux" module name in commonJS module. HOT 1
- Suppress webpack `Critical dependencies` warning message HOT 3
- $listenTo a store in a nested directive that have an isolate scope HOT 4
- $listenTo never updates the scope
- Emit custom event from each store HOT 1
- browserify the src is unsuccessful HOT 8
- controllerAs HOT 2
- No digest after server data was retrieved. HOT 6
- Best Way to object update in a collection HOT 1
- Dependency of angular HOT 1
- (Ignore, created issue on accident, apologies)
- Some $listenTo callbacks run outside of the digest cycle when Baobao is in asynchronous mode HOT 2
- Latest releases are not available via NPM HOT 2
- webpack2 report error HOT 2
- Question: How to map const action to function helpers? HOT 2
- QUESTION: How to attach store from another module? HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from flux-angular.