GithubHelp home page GithubHelp logo

Comments (37)

timkindberg avatar timkindberg commented on August 24, 2024

I like $state.go.

  • Would go be able to accept an object-based state parameter? e.g. $state.go(contacts)
  • For navigating to child I like '.' instead of '@' because it matches our state naming convention where as @ is being used for multiple views, so like this $state.go($state.current.parent)
  • For navigating to a parent or root I would think we could do something like $state.go($state.current.parent) (or jsut $state.parent) and $state.go($state.root)
  • For navigating to a sibling, who knows maybe $state.go('..siblingName'), two dots. Or '^name' or '>name'. I thought of '..' because its like navigating folders "../foldername" would get you to a sibling folder, but I didn't want to introduce the slash for fear it would start to feel like urls.

from ui-router.

nateabele avatar nateabele commented on August 24, 2024

As stated in #14, the traditional way to build a state machine is by defining events that specify valid state transitions. Using proper definitions, it is possible to determine which transitions are valid for each state, and preventing the state machine from reaching an unstable or unexpected state. Here's the canonical car example:

// Assume the following available states: "parked", "stalled", "idling", "firstGear", "secondGear"

.event("park", {
    from: ["idiling", "firstGear"],
    to: "parked"
})
.event("start" [
    { from: "stalled", to: "stalled" },
    { from: "parked", to: "idling" }
])
.event("idle" {
    from: "firstGear",
    to: "idling"
})
.event("shiftUp", [
    { from: "idiling", to: "firstGear" },
    { from: "firstGear", to: "secondGear" }
])
.event("shiftDown", [
    { from: "firstGear", to: "idling" },
    { from: "secondGear", to: "firstGear" }
])
.event("crash", {
    from: "*",
    to: "stalled"
]);

The events then become methods available on $state objects. Then, rather than there being any ambiguity, or possibility that the machine could transition into an unexpected state, the possibilities are clear:

// parked:
$state.start() // => "idiling"

// idling:
$state.shiftUp() // => "firstGear"

// firstGear:
$state.crash() // => "stalled"

// stalled:
$state.shiftUp() // => Error: Invalid state transition

The syntax here is just an example, the important part is the structure and semantics.

Another great feature of proper state machine events is that it makes wizard-like prev()/next() setups trivial. You just keep calling next() on each state to move to the next one.

This also solves the 'can we get there from here?' problems with transitionTo() (which would still exist in a generic 'event' like go()): you just need to do a reverse lookup to see if a valid event exists.

from ui-router.

timkindberg avatar timkindberg commented on August 24, 2024

I think I love this! Should this a separate issue? Definitely needs to be explored!

from ui-router.

nateabele avatar nateabele commented on August 24, 2024

Having a separate issue is up to you. I just thought it made sense to put this here, since the events you define would be your high-level transition methods (and we can bundle parameter-passing up in them, too).

The other cool thing here, obviously, is that it takes the complication of explicit state references out of your templates.

from ui-router.

timkindberg avatar timkindberg commented on August 24, 2024

Yeah ok, keep it here then. So would we then also do the hooks too like this?:

.event("park", {
    from: ["idiling", "firstGear"],
    to: "parked",
    before: fn(){},
    between: fn(){},
    after: fn(){}
})

from ui-router.

nateabele avatar nateabele commented on August 24, 2024

That could work, but you'd be more limited in how you apply them (for example, some hooks might apply over several events, so that could be pretty constraining). The syntax would at least need to be different though, because the second parameter needs to be able to be an array or an object hash.

from ui-router.

gregwebs avatar gregwebs commented on August 24, 2024

This is not a good example for the project: it is a traditional FSM example, whereras we are dealing with application state. I found in my production application that there was no need to define transitions between states: instead the enter/exit callback hierarchy handled everything perfectly with the exception of first restricting based on login. It is probably important to this that StateTree supports concurrent sub states, which are much more difficult to figure out how to map to routes.

Defining transition events should be most useful for placing restrictions, so you might consider going back to #14 to discuss this. Either way you should make sure to use a real-world application routing example.

from ui-router.

timkindberg avatar timkindberg commented on August 24, 2024

@gregwebs are you saying the before, between and after are not good, or the $state.event idea? I'm not quite sure how we'd use the transition hooks, but I do see how $state.event could be very helpful for high level navigation and actions (as @nateabele pointed out it could be used for prev/next actions as one example). We've heard from at least @jeme on actions as well.

from ui-router.

gregwebs avatar gregwebs commented on August 24, 2024

I found the entire concept of event transitions ($state.event in this case) to be unnecessary. I just go to the state and the enter/exit callbacks handle things.

I don't think I understand the next/prev suggestion. That implies a linear ordering of states, but even in a traditional FSM one jumps around based on conditions & events. It seems that under an event setup a user would have to make an extra effort to define a chain of states that work based just on a next() invocation.

I think it could only be done automatically in our hierarchical setups as a way to move up & down from parent to child states when there is only one child or to move across sibling child states of a parent.

from ui-router.

nateabele avatar nateabele commented on August 24, 2024

I found the entire concept of event transitions ($state.event in this case) to be unnecessary. I just go to the state and the enter/exit callbacks handle things.

If you had a couple of examples handy, that might help with a point/counterpoint. I'm not committed to one way or the other right now, but in my experience having events be a separate thing helps to keep hook points DRY.

I don't think I understand the next/prev suggestion. That implies a linear ordering of states, but even in a traditional FSM one jumps around based on conditions & events.

This was just one off-the-cuff example explaining polymorphism of events and transitions, i.e. that the same event can be called in different contexts, with relevant contextual effects. In a linear wizard-like example, for one thing, it's possible to change your event definitions without having to poke through your UI bindings. Hope that makes sense.

from ui-router.

timkindberg avatar timkindberg commented on August 24, 2024

I don't think I understand the next/prev suggestion. That implies a linear ordering of states, but even in a traditional FSM one jumps around based on conditions & events.

Yeah it wouldn't be a permanent API fixture, the next/prev events would be added by the user as needed. Similar to how the shift up/shift down events work in @nateabele's car example.

from ui-router.

ksperling avatar ksperling commented on August 24, 2024

I tend to agree with @gregwebs on this -- named transitions with actions associated with the transitions are obviously the bread and butter of traditional state machines, but I think those are quite different from our use case here.

I think it would really help to have some real world use cases of what sort of things you'd actually want to do in those "when going from A to B, do this" hooks. The provisioning of the UI templates / controllers is taken care of separately already.

I can't think of much myself -- even onEnter I don't see being used particularly often, and cases where i'd then additionally want to do different things based on what the previous state was seem rarer still. Happy to be convinced otherwise though.

We're still talking about web application UI's here... The idea that the same "page" in the app would behave radically different depending on how you got there seems to imply a rather odd user experience (unless you're building some game where each state represents a "room" or something like that...)

from ui-router.

timkindberg avatar timkindberg commented on August 24, 2024

Ok @ksperling. It is an interesting idea and I think users would think it was really cool, but you are right that maybe it's not needed in a conventional website state management tool. If @gregwebs is saying we don't need it, then we probably don't (I mean he DID create a state tree library). So let's table that for now, @nateabele can write it up as a separate issue at this point and label as "review later" if he still feels strongly.

Back to the main issue then... high level methods. You really have to now go back up to my first comment in order to get my initial feedback. https://github.com/angular-ui/router/issues/15#issuecomment-13609283

from ui-router.

ludinov avatar ludinov commented on August 24, 2024

Some common scenarios of using state transition events:

  • "ui loading state" while waiting for resolver (e.g. long ajax request)
  • redirect to parent (or another state) state on rejecting transition
  • For example direct link /blogs/3/posts/4/comments/5/autor mapped to 4 nested states and sequence of 4 ajax requests, and now until we resolve the last one, user will see just blank screen, instead of step by step ui transformation progress. May be it's possible to define "safe" state to enter in the middle of the chain.
  • For example I want to prevent user to leave the view (state) without confirmation, if he change some form input fields, but didn't save the form. So I need to have ability to prevent transition on exit state, (return false in onExit or rejected promise or throw exeption) and need to have access to the scope in handler or share something with controller. (e.g. with $state or another injectable)

I think transition should be chain of promises amendable for each step.

e.g. : app.user.details.edit => app.admin.users.list

chain:

app.user.details.edit : leave
app.user.details      : leave
app.user              : leave
app                   : (maybe some 'change substate' event ?)
app.admin             : enter
app.admin.users       : enter
app.admin.users.list  : enter

just "pseudoapi":

onTransition(from, to, transitionHandler, fallbackHandler)

onTransition(
    '^',          // '^' = parent,
    'app.admin',  // '*' = any, '-' = sibling
    function(..injects..) {
        return auth(); // promise
    },
    function(..injects..) { // executed after main transition rejection logic
        $state.goTo('app.login');
    }
)

But this type of api doesn't solve some problems:

  1. should we move to (render) intermediate state before full chain resolution ?
  2. how can we control ui somehow inside handlers to show some progress to the user during transition?
  3. how to communicate with state controllers scopes inside of handlers (may be with some shared injectable or with params..) ?

Btw $state.go('admin') could return promise, that we can control explicitly:

...onClick = function() { 
    $q.when()
        .then(ui.overlay.show)
        .then(function() { $state.go('admin.dashboard') } )
        .then(null, function() { $state.go('login') })
        .then(ui.overlay.hide, ui.overlay.hide)

or $state.go('admin') could return promise maker function to beautify syntax:

    $q.when()
        .then(ui.overlay.show)
        .then($state.go('admin.dashboard'))
        .then(null, $state.go('login'))
        .then(ui.overlay.hide, ui.overlay.hide)

but where to place this logic ? Definitely not in controller, but $state.go inside state transition event handler looks also weird...

from ui-router.

timkindberg avatar timkindberg commented on August 24, 2024

I think the loading indicator could just be a loaded property that is true once all views and dependencies have been loaded and resolved. The you can use the property in you templates to show some indicator if loaded while false. I think this should be a separate feature request because its SO common.

We may be able to do state rejection in other ways see the issue related to that topic. Lets bring redirects into that discussion.

Your 3rd bullet: interesting idea. Maybe add a needsToLoad property to the view object to specify which views aren't vital to the page to display.

The chain you specify us already happening behind the scenes but @ksperling would have to confirm that each ancestor dispatches its own event. There is another issue talking about dispatching. Perhaps we need more events to be dispatched; one per every exit and enter.

No comment on the other stuff for now.

from ui-router.

jeme avatar jeme commented on August 24, 2024

The provisioning of the UI templates / controllers is taken care of separately already.

That might actually turn out to be a rather annoying limitation. I choose to have a "view service" or "view provider" if you will... which basically allows to:

$view.set('main', '/tpl/user/dashboard.tpl', DashboardController);

Controller being optional, but that means that views can be exchanged on the fly just as is possible with ng-include linked to a scope property.

(The way it works actually gives the incremental rendering of views as requested above, not something I particular aimed for though)

from ui-router.

ksperling avatar ksperling commented on August 24, 2024

@jeme One problem I have with the 'step by step' idea is that the intermediate steps are not necessarily valid states in the state machine sense. In the sample app, going from "about" to "contacts.detail" would pass through "contacts", but that is an abstract state that cannot be meaningfully navigated to by itself (the whole set of exit/enter callbacks is still invoked though, but only after all the asynchronous resolves have completed). There are multiple useful aspects to doing the state transition atomically:

  • It's atomic. Code that looks at $state.current can always be sure that it is looking at a valid state of the UI, not some intermediate.
  • It's atomic. Either all templates and dependencies resolve successfully and that state transition happens, but if something fails (or the transition gets rejected, however we end up doing that exactly) the state remains exactly as it was, not in some undefined intermediate state.
  • All the asynchronous work of resolving templates and dependencies happens in parallel, and therefore in general takes less time.
  • If a child state overrides a view also define by a parent state, we don't end up first loading the parent view and then discarding it again shortly after.

I guess the incremental loading may be desirable in some cases, even though I'm not sure this really needs to be done at the state machine level -- if its the exception rather than the rule in your application you can always do some additional async work in the controller after the view has been rendered. It might be feasible to implement your 'safe state' idea -- even though I'd probably call them 'eager' rather than safe -- it seems like error handling on the application level would become rather tricky though. I think the simplicity and robustness of atomic transitions probably makes them the right choice in 98% of cases, so to me incremental transitions would be in the "investigate for v2" category at the moment.

The idea of a separately usable $view service is interesting; the interface between ui-view and $stateProvider is currently $state.$current.locals, which is a map from view names to views (template/controller/resolved deps), so rather than just setting that directly we could think about exposing that touch point as a service.

We have to think carefully about how this would work in terms of asynchronous loading -- $stateProvider only "pushes" a view into this global data structure when everything is fully resolved, wheras your $view.set(...) example still requires things to be loaded before the view can actually be 'set', so maybe should be called $view.load(...) instead. We'd need to be careful to make sure that overlapping calls to $view.load() have a sane outcome (last call wins i guess), and how this would interact with concurrent synchronous $view.set() calls as they would be done by $stateProvider. The behaviour when you manually update a view that $stateProvider also manages could also be problematic.

Another question is how clearing views would work -- $stateProvider currently simply pushes all views assigned by the current state in one atomic operation (by replacing $state.$current), which implicitly also clears any views not assigned in the current state. If views can be set via $view directly, it seems on a transition A -> B $stateProvider would need to work out and explicitly clear any views assigned by A that aren't assigned in B.

I can think of some vague use cases for $view, e.g. having some sort of contextual assistant view in a side bar that is set dynamically based on user input or some other conditions. Do you have any concrete use cases for this feature in your apps? Maybe we should open a separate ticket for $view and work out how it would work in detail, and see if there are enough use cases to justify the added complexity (which hopefully won't be so big).

One thing I do quite like about $view would be that it allows people who don't like $state for some reason to implement their own state machine while reusing $urlRouter and $view, but this is a somewhat niche feature as we should aim for $stateProvider to cater to the majority of use cases.

from ui-router.

jeme avatar jeme commented on August 24, 2024

@ksperling It wasn't me who brought the step-by-step up, it was mostly a desire i gathered from @ludinov's response...

One thing I do quite like about $view would be that it allows people who don't like $state for some reason to implement their own state machine while reusing $urlRouter and $view, but this is a somewhat niche feature as we should aim for $stateProvider to cater to the majority of use cases.

Thats not the only thing it does to me, it also separates responsibilities/concern, and even if it is not for the outside to use, that would certainly be beneficial from an inside perspective as well...

Adding the necessary details to support what you refer to as atomic would properly also be possible, since all my current state transitions is by very definition atomic, I don't have the issue you mention, the reason I get incremental reload on certain occasions is because I in my usage of the implementation perform multiple transitions.

from ui-router.

nateabele avatar nateabele commented on August 24, 2024

@ksperling I was gonna start working on parameter inheritance for $state.href() / ui-sref. Maybe that could be the basis of a short-hand wrapper method for transitionTo(). Would that step on anything you're working on currently?

from ui-router.

ksperling avatar ksperling commented on August 24, 2024

Go ahead, I'm still working on the 'resolve' stuff. Maybe we should just include it in transitionTo directly at this point -- the idea of a wrapper was that somebody could decorate $state to modify it's behaviour, and still take advantage of sugar layed on via wrapper functions, but I don't think that sort of use is common (or happening at all?) at the moment.

from ui-router.

nateabele avatar nateabele commented on August 24, 2024

@ksperling Yeah, not really. Generally, I doubt states will be nested deeply enough to make the idea of i.e. $state.go("..") really matter. Btw, what do you think about changing the 3rd param of transitionTo() an options hash? So, for example $state.transitionTo("foo", params, { location: true, inherit: false })?

from ui-router.

ksperling avatar ksperling commented on August 24, 2024

Yes, third parameters should become an options hash, which then internally becomes the "transition" object that gets passed to onEnter/onExit etc instead of the current separate to/from/toParams/fromParams etc.

from ui-router.

ksperling avatar ksperling commented on August 24, 2024

Being able to say ".." is less about saving typing and more about decoupling states from their parent where that makes sense.

from ui-router.

nateabele avatar nateabele commented on August 24, 2024

Okay, sounds good. I'll get started on it, but you'll have to show me how you want it. Re: "..", that makes sense. Should that be baked into transitionTo() as well?

from ui-router.

ksperling avatar ksperling commented on August 24, 2024

Maybe add an optional 'base' parameter to findState() so that if the first parameter is given as a name and base is specified it resolves the shortcuts.

We have to think about what we want the syntax to be... I think we should avoid "/", because it would make stuff look too much like URLs, which will be quite confusing because the state nesting is often similar to but different from the URLs. Maybe ".." for parent "..." for grandparent, and "&" for current state? All being treated as prefixes, so you can say "&child", "..sibling", "...uncle"

It's also worth thinking about if we want these to effectively be string operations, or how we handle the case where a state specifies it's parent explicitly rather than via the dot syntax. I think it makes sense to resolve the up references by walking the state.parent tree, and then treat a suffix (if present) by appending "." + suffix to the name of that state.

from ui-router.

ksperling avatar ksperling commented on August 24, 2024

(I think we should allow any number of "." for up navigation, even though in practice people probably won't use more than 3 or 4 at most)

from ui-router.

ksperling avatar ksperling commented on August 24, 2024

As an aside, it seems to me that while the "parent.child" way of naming states and implicitly setting the parent is nice when there is tight coupling between the child and the parent anyway, but that the explicit parent setting is nicer when they are essentially independent, as it makes it easy to move the child to a different place in the hierarchy / navigation structure of the app without having to rename any states.

The ability to move states around by having them only loosely coupled to their parent is also something that needs to be considered in any changes to the view naming approach.

from ui-router.

mgonto avatar mgonto commented on August 24, 2024

Commenting to get notifications

from ui-router.

nateabele avatar nateabele commented on August 24, 2024

@mgonto Alternatively (to spare notifications for future dev teams), there's a menu at the bottom. ;-)

from ui-router.

mgonto avatar mgonto commented on August 24, 2024

Ohhh! I've never seen it before!. Sorry for the notification.

Next time I'll use the button. It should be at a more visible place.

Thanks!

from ui-router.

timkindberg avatar timkindberg commented on August 24, 2024

May be too late, but I was thinking it may be nicer and easier to read if we use ^ instead of . for traversing upward. I like how the arrow points upward. Then maybe we could use > for sibling, because the arrow points sideways. So its like up and over.... Just throwing some thoughts out there...

from ui-router.

ksperling avatar ksperling commented on August 24, 2024

I like "^". A separate symbol for sibling doesn't seem necessary though; wouldn't that just be "^sibling"? or maybe "^.sibling" I kind of like the look of the latter. "^^.uncle" seems alright too, more obvious than "..uncle" I'd say.

The only drawback is that "^" from the regex analogy (and how we want to use it for URL patterns) anchors at the very top, not just one step up.

from ui-router.

eahutchins avatar eahutchins commented on August 24, 2024

Any update on when this might be landing? I could help out if needed.

from ui-router.

nateabele avatar nateabele commented on August 24, 2024

I'm actually pretty close to done with a pretty big refactor which includes these and other enhancements, and I'll be in the air with no internet for 16 hours starting tomorrow afternoon. By the time I land, UI Router will be a whole new library. ;-)

from ui-router.

timkindberg avatar timkindberg commented on August 24, 2024

Looking forward to seeing your changes!!!

from ui-router.

timkindberg avatar timkindberg commented on August 24, 2024

I'd say this is done. @nateabele?

from ui-router.

nateabele avatar nateabele commented on August 24, 2024

Yup.

from ui-router.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.