- No longer encourage/allow external calls to a Route's
- Instead, make use of a new
routeToevent, with the same semantics as
transitionTo, only that it's an event that routes can respond to.
- ApplicationRoute will have a default
routeTohandler which passes its args to an internal
- URL changes will be converted into a
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
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.
The Hard Part: URL-based Transitions
It's simple enough to convert your app's
transitionTo's, if they
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
routeToevent 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 a
routeTohandler 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 generated
routeToevent 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.
Possible Solution 1: Context Resolvers
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
deserialize. This would solve the problem of not immediately
deserialize once a URL change occurs, letting the
handlers along the way decide whether that data should be fetched.
But, for a transition to a nested route, like
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.
Possible Solution 2: Lazy Promises
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
it. This would eliminate the need for context resolvers, but would
require some special coding to implement the lazy promise class.
Finally: Shall We Resurrect Per Route Loading States?
In router v1, there was a concept of per-route loading states, where if
deserialize return a promise (an object with a
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?