- No longer encourage/allow external calls to a Route's
transitionTo
method. - Instead, make use of a new
routeTo
event, with the same semantics astransitionTo
, only that it's an event that routes can respond to. - ApplicationRoute will have a default
routeTo
handler which passes its args to an internaltransitionTo
. - URL changes will be converted into a
routeTo
event event.
This is an attempt at the simplest possible primitive to allow routes to halt,
redirect, or decorate transitions, while maintaining a grounded
state-based approach to handling these scenarios.
Beforehand, the state machine paradigm was broken
by controllers and linkTo
ruthlessly commanding the router to change
to a particular state via transitionTo
, but with state machines, the
only interaction from the outside world should be with events. Hence the
routeTo
event.
There's been been a lot of talk about preventable transitions, but the end result of this proposal is no specific solution to such a thing, but rather just the primitives to allow for sensible solutions to this problem down the road.
It's simple enough to convert your app's transitionTo
's, if they
exist, into routeTo
events, since their semantics are the same, but
transitions via URL have the following complications:
- They must deserialize a requested URL into context objects that will
be passed to the routes. (With
transitionTo
, you directly provide these context objects.) - Deserialization of leafier route parameters must happen after parent routes' deserialized promise contexts have resolved, since a leafier deserialization may depend on the data of a resolved parent promise.
- Since the goal is to convert to a
routeTo
event that can be intercepted/handled by any route, there needs to be a way to avoid immediately deserializing these context objects and potentially causing server requests when arouteTo
handler doesn't need that information to know to halt / redirect the transition. For example, if I'm on a route with a half-filled in form, and I navigate to/posts/123
, the form route ought to be able to handle the generatedrouteTo
event and prevent it from bubbling, without the post route's deserialize being called and firing a request to the server to load data for Post 123.
Rather routeTo
handlers expecting context objects, they would be
passed a context resolver, which is nothing more than a function you can
call that will return the context object. In the case of a URL-based
transition, a context resolver would simply return the result of a
route's deserialize
. This would solve the problem of not immediately
firing deserialize
once a URL change occurs, letting the routeTo
handlers along the way decide whether that data should be fetched.
But, for a transition to a nested route, like /posts/123/comments/456
,
which would get converted to a routeTo
event with a Post promise and a
Comment promise as contexts, what would the context resolver for Comment
yield? It would have to yield a promise that only resolved after
- The Post promise resolved, and
- The Comment promise resolved.
with both of these conditions happening sequentially. So, basically, context resolvers from URL changes would have to yield somewhat complicated promise chains depending on the resolution of parent route promises. So, it gets... complicated.
Solution 1 is solid, but it's a little annoying to have two different
concepts of "deferred" values: context resolvers, and then promises.
Perhaps another way to go would be to implement a "lazy" promise, which
doesn't try to resolve itself until someone's actually called .then
on
it. This would eliminate the need for context resolvers, but would
require some special coding to implement the lazy promise class.
In router v1, there was a concept of per-route loading states, where if
a route's deserialize
return a promise (an object with a .then
property), the router would go into a loading state for that route, if
one was specified, and it would remain there until the promise resolved,
and then the transition would continue.
In the present router, there's still a concept of a loading state, but it's not specific to a route, but is little more than a global loading flag that you could, for instance, use to show a loading spinner until the transition has completed. The router doesn't actually perform any sort of teardown or state transition until all the promise objects have been resolved, so any events sent to the router would be handled be the pre-transition state.
So, the major question that remains is:
Do we resurrect per-route loading states? If so, we need to find a
way with the new router DSL to signify that a route has a loading
state that should be entered when its deserialize
returns a
promise. Or do we just assume that each state has an implicit loading
state? If we don't resurrect per-route loading states, are we happy
with a single global loading "flag"? And are we happy with only
actually performing the transition (template teardown, controller
setup, rendering templates, etc) once the promises have resolved?
Or perhaps we don't make the router transition pause while waiting for these promises to resolve. Maybe we just perform the transitionTo and immediately pass these promises to their routes and let them handle the async-ness. We've already gone through the complicated work of assembling a chain of promises that depends on parent promises resolving, so perhaps this will do the trick?