-
Notifications
You must be signed in to change notification settings - Fork 3k
RFC: state machine guard function #618
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I really like this! It's a great alternative option for guarding states. However I do think you will be writing more code this way. Now instead |
Perhaps allow it to be inherited down the state tree by default... |
There was already some talk about the ability to abort transitions in |
I am also really interested in this. The code above looks great, however I would like to add in the possibility of a redirect. This would be useful if say you wanted the blocked user to be sent to a specific page, or to stop them being sent to your .otherwise() route if they we're navigating directly to this state.
I agree it should be possible to inherit this down the state tree, would this be easy to implement? |
Again, though: why not allow |
The main reason I can think of is to avoid loading up resource intensive states only to find you don't need them. Having looked through the code a bit more and taking in the potential for doing inherited rules, I think it would be possible to add it into the following:
I feel other areas may need to be patched to fit, but this would allow the parent to resolve before the child's rule is checked. In a complex application this could easily catch a rule before a complex resolve is executed, hence increasing speed. Another option would be to allow resolve functions to abort transitions and do redirects. This is where I went first to get this to work, but my redirect in the resolve was reverted when it got back to carrying out the initial transition. |
For as many instances where that's the case, there are likely just as many where one would need said resources in order to make that decision. Furthermore: app.config(function($stateProvider) {
$stateProvider.state('privatePage', {
data: {
rule: function(user) {
// ...
}
});
});
app.run(function($rootScope, $state, $currentUser) {
$rootScope.$on('$stateChangeStart', function(e, to) {
if (!angular.isFunction(to.data.rule)) return;
var result = to.data.rule($currentUser);
if (result && result.to) {
e.preventDefault();
$state.transitionTo(to, result.params);
}
});
}); ...and, done. No internal changes required, which means you have complete control over the implementation, which avoids future RFCs requesting additional configurability for whatever theoretical solution we might implement, which means I have more time and energy to spend implementing things this module isn't currently capable of, rather than merely making things that are already possible 10-lines-of-user-code easier. |
Nate, I understand what you are saying and I completely agree that you shouldn't have to spend your time making things 10-lines-of-user-code easier. I am very grateful to you for supporting this library and all the work you have put into it. I am also sorry that this has turned into what essentially looks like a Stack Overflow problem. However, I had already tried your solution above and it did not fix my problem. Here is a plunkr showing exactly the problem I am having: http://plnkr.co/edit/IwTn4q?p=preview As you can see, if someone shortcuts the middle state (normally done through url routing), the app allows you to view the banned page. You can't then revisit this page since the resolve has updated the dataStore (this would normally be a data request, so the client wouldn't know the outcome until the resolve had completed). If there was some kind of broadcast telling me each resolve down the tree had finished, then that could potentially be used, but this seems awkward. I also tried using $stateChangeSuccess to catch the change. Although this does fix the problem it has a visible flicker which could end up being longer if more was actually going on. I am happy to try to work out a fix myself. I'm very new to the magical world of open-source but I am very keen to pull my own weight and thought this might be a good way to start out. Let me know what you think? If there is an obvious way around the problem then I'm happy to use it. If not then we could discuss exactly what a theoretical solution should do and I can try my best to implement it :) |
No worries. The best solutions are produced by rigorous discussion and as a result, @timkindberg has a new code example for the FAQ.
Yeah, it was kinda off-the-cuff. Probably something wrong with the syntax. I'll take a look later tonight or tomorrow. |
@nateabele Sorry, this is not a best solution. The main problem is what i should define rules in separate place (file) and i don't like this separation. Please provide ability to abort transaction in ...
...
onEnter: function(event, $state) { event.preventDefault(); $state.go('previous_page') } But this abort should be triggered before controller runs. This is main desire :) Good example of state machine implementation: https://github.com/pluginaweek/state_machine Is it reasonable for you? |
@pechorin No, not quite. That's a good example of a classical FSM. That's a bad example of a hierarchical state tree of the kind
Yup, that's the idea. |
My approach to handle state constraints: https://gist.github.com/pechorin/7911719 the main idea is forget about angular-events and define constraints in dsl-like manner: app.run(function($stateConstraints) {
$stateConstraints.protect({
state : 'myState',
guard : function($currentUser, $state) { // <- any service
// guard should return FALSE if transition to state should be cancelled
if ($currentUser.isSignedOut()) {
$state.go('other')
return false
}
};
onFail: function() {
alert("Signed users only");
}
});
}) |
Okay, if you think that this kind feature is unnecessary and we should cancel state events inside controllers then i think i should publish this code as lib :) |
Yeah, I would definitely recommend creating a library for it. There are lots of different valid approaches to access control (of which this is one), and different people will want to do different things. |
Hey Nate, did you manage to have a have a look at my Plunkr? http://plnkr.co/edit/IwTn4q?p=preview Unfortunately I think it means I can't use pechorin's method. I'm still tempted to write something custom but let me know if you still think using the onEnter function is the way to go. |
@josh-hobson I took a quick look over it, yeah, but didn't have time to think through the full implications of each possible approach. To be clear, though, the |
@josh-hobson i fix your code: http://plnkr.co/edit/Iw7SnmcQllAIaNjVKdvc?p=preview now it works :) |
@nateabele can we help? :D |
@pechorin Yeah, you could work on getting the callers of Let me know if you're interested in working on this, and if you need any help along the way, especially with the tests. |
...and added. I do so much. |
Only thing that is (potentially) lame is that onEnter and onExit run after all resolves for the new states are resolved... good if you need the resolve, but bad if you don't, then its a performance problem. |
@timkindberg Yeah, that's the idea behind the solution I presented above. If you need something simple where rules can be attached to states, use that. If you need something more complex that requires resolves, use |
@nateabele ok yes I see. I'd wondered if these guys were thinking onEnter/onExit would be able to do it all somehow... but it won't. |
Just going to throw this out there since this is an RFC.... It would be nice to be able to defer the transition until after an asyc function can return judgement as whether or not the user is permitted. Say I need to validate something server-side, I need to make that call and when ever it gets done I can decide transition or not. One should be able to return a promise and have the transition wait until it is resolved or rejected or something like that. Also, it should be easy to override the |
Thanks @timkindberg Also, does |
Hmmm, I guess you can't do async checks in $stateChangeStart... you can do them in $locationChangeSuccess though. https://github.com/angular-ui/ui-router/wiki/Quick-Reference#urlroutersync |
@TheSharpieOne back to your original point, yes I now see, I agree that |
If you use the preventDefault technique then presumably you're supposed to satisfy some condition before you call transitionTo so the next time stateChangeStart fires you wouldn't run your preventDefaultstuff again right? |
@TheSharpieOne is this what you mean by returning a promise and waiting for it to be resolved or rejected? http://fiddle.jshell.net/L9jxf/1/ |
@mfield Yes, just like that or at least very similar. That is a very interesting place to put it. It fits the needs for my scenario and works in the current solution, though it would be better if it was somewhere more abstracted. |
@stu-salsbury yes that's what I was thinking too, but I think I came to the conclusion that we would need some additional methods other than So I had some ideas:
|
Wait hold on... @TheSharpieOne Can't you just set the |
I still don't get why the condition that caused the initial "side route" to be taken wouldn't be cleared up before you re-enter |
Does setting |
@TheSharpieOne Oh crap, yeah, the view updates itself by listening to the state event. Damn... so I'm thinking we have some options: @stu-salsbury I think you may be wrong, here's a breakdown of the steps that happen:
So if you are technically allowed to access the 'to' state you'll continuously be "allowed" to go to it, but never get there, because the state will constantly be preventDefault'ed. Am I missing something? |
@timkindberg @TheSharpieOne It looks like the view may no longer update via the state event on this branch: https://github.com/angular-ui/ui-router/tree/viewpath. Perhaps the issue being discussed will be solved more easily on top of the refactoring @nateabele has been doing? |
@dlukez interesting. That would mean notify false would then work. |
@timkindberg -- I was under the assumption that something in the general state of the application (e.g. some value in a service property such as myDeflector.dontDeflect == true) would indicate that preventDefault shouldn't get in the way, once you've satisfied whatever async stuff you needed to do. |
@stu-salsbury ah ok, I think I see now. |
Your |
Did we even decide anything here?? |
@timkindberg -- Being able to support async calls in $stateChangeStart would be a great feature for me. Did this request ever go anywhere? I currently use a state filtering system to guard states that I ported over from ng-route. Filters can be chained together and the whole filtering system works with promises. Currently I have the filters run as a resolve. This works great because resolve works with promises. However, this gets complicated once child states define additional resolves that get data since all resolves execute at the same time. You can wrap those additional resolves with the filters but then they execute twice which I would like to avoid. If $stateChangeStart dealt with promises then this would be much easier to deal with. I think your ideas above would work well and provide the capability to handle a great many scenarios to prevent, delay, or continue state transition. These ideas were
I think Here is a sample of what could potentially be done if $stateChangeStart event supported your proposal and adjustments to halt. http://plnkr.co/edit/Uje7kbvZfRX37yTyeEV6 This plunker doesnt work but its an idea of what could be. This sample would block the state transition until a current user was logged in and a permission could be checked. |
So far as I am aware, attaching custom methods to the |
It seems like @jnapier might be implying that those functions on the event object could be created and supported outside of Angular's purview and with no help from it? You could presumably attach them to a parameter instead, since that is what you have control over. |
@nateabele - I was just following @timkindberg's lead. @stu-salsbury is correct, that event object doesn't necessarily need to be the one that indicates state transition control flow . An additional argument could be used to indicate control flow. Ui-router just needs to know what to do on pause, resume, and hault. All that would have to happen in $state.transitionTo |
Hey I just come up with the big ideas. I don't think about their feasibility! Technically we could extend the event object though it may not follow convention. |
@timkindberg There is no reliable way to extend an event object. When you dispatch an event, the event object is factoried inside Angular core, and the dispatching code has no chance to touch it. The event is then routed to the first configured event listener. Hence, the only way to manipulate the event object is through a listener, and there is no way to ensure that a listener applied in ui-router itself would supersede a user's. Hence, no way to ensure that a hypothetical event object method exists before it is called. |
I seem to be getting .controller('AppCtrl', function ($scope, $state, $kinvey) {
$scope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
if ($kinvey.getActiveUser() === null) {
$state.transitionTo('login');
event.preventDefault();
}
}); Is there any workaround for this? If I put a log inside the conditional, it gets called nearly 1000 times? |
@dmackerman When you transition to login, it is probably triggering the You could set |
@TheSharpieOne: good call. 👍 |
This is implemented in 1.0 with |
Hi everyone. I develop site with roles system. Sometimes user can access some states and sometimes not.
The easiest way to handle this constrains is:
This is okay solution, but not in cases where we have many states and many different constraints. This produce many event handlers and personally i think what guard constraints is a "state machine" responsibility ;) Also i don't like idea to define "guard functions" in separate place. I think this constraints should be defined in-place like this:
I patch some internals and implementation looks pretty easy:
So if community like this idea i will provide patch and tests. The main motivation for me: "State machine should handle guard conditions by self".
The text was updated successfully, but these errors were encountered: