-
-
Notifications
You must be signed in to change notification settings - Fork 407
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
Create Router Service #95
Conversation
|
||
A RouteInfo object has the following properties. They are all read-only. | ||
|
||
- name: the dot-separated, fully-qualified name of this route, like `"people.index"`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having the name is great but do you think it would be worth giving the instance or the class as well? I would imagine you could use ember with something that allows you to specify "query fragments" (Falcor) in your routes and potentially components, if you have a way of collecting those fragments recursively.
For example you could imagine that in node you could do something like this and prefetch all the data for the initial route:
let indexHTML = fs.readFileSync(__dirname+'/index.html').toString();
const dataRegex = /%DATA%/;
const buildQuery = (handler, query = []) => {
query = query.concat(handler.instance.collectQuery());
if (handler.parent) {
buildQuery(handler.parent, query)
}
return callUpstreamService(query)
}
app.collectQueryFor('/profile/123').then(buildQuery).then(data => {
let indexHTML = indexHTML.replace(dataRegex, JSON.stringify(data));
res.writeHead(200, {
'Content-Length': indexHTML.length,
'Content-Type': 'text/html'
});
res.write(indexHTML);
res.end();
})
This would allow you to extract the data requirements without a lot of overhead. If not, this can be proved out in user land as you should be able to lookup the route instances from the info.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to encourage people to do anything remotely stateful with Route instances. I think it's dubious that they are instances at all.
Your overall scenario is an important one, and it comes down to enabling routes to resolve their promises in parallel instead of in series. That would let your data layer be Falcor or GraphQL-like and automagically batch and combine the requests generated in each of the model
hooks. There would be no need to do a new top-down query building step -- the hooks we have are already where routes tell us what data they need.
That whole feature is probably a different RFC. I think it would (1) cause routes to be non-blocking by default (meaning their children's model hooks begin to run even before their own model hook resolves), (2) make modelFor
return a promise, so that you can re-introduce explicit dependencies when you need them, and (3) provide an optional way to declare a route as blocking, which can still be necessary if you're going to do data-dependent redirections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree on all points.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 on routes not being instances. Making them stateful prevents all sorts of cleverness for solving exactly the problem Chad brings up. The separate RFC is #97.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think they are fine being internally/privately stateful, but not good being singletons.
A few random thoughts/notes:
|
@ef4 we have a similar service now. There're two things that we're using that could be part of the public interface. It's easy to build those on top of the current public interface, but something to consider.
Our RoutingService is also Ember.Evented, so a consumer can subscribe to the |
|
||
### Deprecation | ||
|
||
I propose deprecating the publicly extensible `willTransition` and `didTransition` hooks. They are redundant with an observable `currentRoute`, and the arguments they receive leak internal implemetation from router.js. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dropping didTransition
and willTransition
is likely going to be very painful. Basically anybody who ever implemented analytics used those hooks. Some people even (read, me) stored stuff on the transition object and pulled it back out later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. Maybe we need to provide a new event that doesn't expose private members.
It also seems better than relying on an observer.
—
Sent from Mailbox
On Tue, Oct 6, 2015 at 2:37 PM, Nathan Hammond notifications@github.com
wrote:
+A
url-for
helper can be implemented almost identically to theis-active
example above.
+
+
+### New Properties
+
+currentRoute
: an observable property. It is guaranteed to change whenever a route transition happens (even when that transition only changes parameters and doesn't change the active route). You should consider its value deeply immutable -- we will replace the whole structure whenever it changes. The value ofcurrentRoute
is aRouteInfo
representing the current leaf route.RouteInfo
is described below.
+
+currentRouteName
: a convenient alias forcurrentRoute.name
.
+
+currentURL
: provides the serialized string representingcurrentRoute
.
+
+
+### Deprecation
+
+I propose deprecating the publicly extensiblewillTransition
anddidTransition
hooks. They are redundant with an observablecurrentRoute
, and the arguments they receive leak internal implemetation from router.js.Dropping
didTransition
andwillTransition
is likely going to be very painful. Basically anybody who ever implemented analytics used those hooks. Some people even (read, me) stored stuff on the transition object and pulled it back out later.Reply to this email directly or view it on GitHub:
https://github.com/emberjs/rfcs/pull/95/files#r41325988
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
willTransition
is especially helpful for globally preventing a route based on some global state. Say for example pending file uploads. Something the "routable component" might not be aware of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ef4 I think we likely want both evented and observable here. It seems like different use-cases would prefer to "subscribe" in different ways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To check would the proposed observable/event be on the router service or the private router?
Seems like an interesting thought to have components subscribe to beforeTransition
or whatever the event would be and could interject their thoughts.
The first thing that comes to mind is a user-form
component within a users.create
route, while the route does not know the user-form
's state until submission (DDAU) the form could listen for the transition and check to see if any data could be lost and alert/confirm, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ef4 By deprecating the willTransition
and didTransition
hooks, do you mean that the willTransition
and didTransition
events would still be available (or something akin to them at least)? Would this be the time/place to request a changeTransition
event (name up for debate) that would be triggered for transitions initiated between willTransition
and didTransition
?
Overall, I really like this idea! I think a routing service is very helpful an will make my previous apps use less private APIs. I think the only downside is consumers relying on this service to not handle routing concerns in the |
|
||
```js | ||
transitionTo(routeName, ...models, queryParams) | ||
replaceWith(routeName, ...models, queryParms) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it might be useful to also introduce more APIs here to get closer to parody with the underlying browser API. For instance it's currently not very clear how to go back a transition. Solving this in user space normally requires you to save off the previous transition to create an adhoc linked list across your app or create a service to keep track of this, which may be subject to getting out of sync. Things get more leaky when you are in a node environment and now need to guard against window
references because the framework didn't provide a good way of handling those cases with a good abstraction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect this would be a tangental RFC
I got linked here in slack, that this RFC might be curious how people are using this.router.router.currentHandlerInfos.forEach(route => {
var routeSegments = routesWithSegments[route.name];
// using route._names instead of route.params because
// the latter's order is not guaranteed
route._names.forEach(param => {
var paramsSource = routeSegments && routeSegments[param]
? routeSegments : route.params;
models.push(paramsSource[param]);
});
}); |
- parent: another RouteInfo instance, describing this route's parent route, if any. | ||
- child: another RouteInfo instance, describing this route's active child route, if any. | ||
|
||
Notice that the `parent` and `child` properties cause `RouteInfos` to form a linked list. So even though the `currentRoute` property on `RouterService` points at the leafmost route, it can be traversed to discover everything about all active routes. As a convenience, `RouteInfo` also implements `Enumerable` over all the reachable `RouteInfos` from topmost to leafmost. This makes it possible to say things like: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm uncertain about Enumerable future, I believe we should transition to iterable
, as that isn't something we have done yet, i suspect we should leave out the enumerable
part here. We can always expose iterability etc. at a later date.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe with this linked list you can get what router.router.currentHandlerInfos
used to get you. I also use route._names
as an ordered version of route.params
mentioned here. Is this something that is covered in the RFC? Is the new RouteInfo.params
ordered?
@ef4 -- By deprecating the Also, would this be the time/place to request a |
I added some comments a couple weeks ago. I'm unfamiliar with final comment period. Are those comments now blocking the merge, or will the merge proceed with unaddressed comments? |
@kellyselden I missed your open question about param ordering.
This seems like a simple and uncontroversial bit of detail to add, thanks for pointing it out. |
After taking feedback and hearing a lot of uncertainty around general-purpose dynamic variables, I'm going to table them in favor of |
Reviewing the original implementation of the I'd like to add a rider to this RFC which allows the subexpression to receive objects. It becomes incredibly annoying in the scenario where we don't investigate the final state to prune values from the query string inside of Handlebars files and makes it more difficult to accomplish standardization of nested query param behavior since everything must still be key/value pairs. Current State: {{#link-to 'some-route' (query-params
key1="value1"
key2="value2"
key3=(if key3 key3)
)}}Some Route{{/link-to}}
{{! key3 if statement required in order to prevent it from being in the output if `undefined`. }}
{{! Serialization happens prior to removal. }} Proposed Approach: Ember.controller.extend({
someQPObject: {
key1: 'value1',
key2: 'value2'
}
}); This would make it sane to adopt my proposed nested query params specification as a unifying pattern across all of our layers: {{#link-to 'some-route' (query-params someQPObject)}}Some Route{{/link-to}} Sketch to update current implementation: positional.forEach(function(positionalArg) { if (typeof positionalArg !== "object") { assert(); } });
var result = Ember.assign({}, ...positional, named.value()); I'm also amenable to disallowing mixed usage and only allowing a single positional param: if (positional.length === 1 && typeof positional === "object" && Object.keys(named).length === 0) { return positional[0]; }
if (positional.length === 0) { return Ember.assign({}, named.value()); } |
@nathanhammond that all sounds good but this RFC is already pretty big and thoroughly argued, and I was hoping not to have to restart final comment period (since my latest updates were all just incorporating feedback from the discussion so far and AFAIK there are no controversial questions outstanding). Done separately, the query params change seems like a short and sweet RFC that can sail through. |
|
||
- default values will not be stripped from generated URLs. For example, `urlFor('my-route', { sortBy: 'title' })` will always include `?sortBy=title`, whether or not `title` is the default value of `sortBy`. | ||
|
||
- to explicitly unset a query parameter, you can pass the symbol `Ember.DEFAULT_VALUE` as its value. For example, `transitionTo('my-route', { sortBy: Ember.DEFAULT_VALUE })` will result in a URL that does not contain any `?sortBy=`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rereading this now with a bit more context and explaining it for future travelers (read: me, six months from now).
This is to ensure that our default behavior of "hydrating unsupplied query params" continues to work. Without this we effectively end up doing Object.assign({}, unsupplied, queryParams);
which would make queryParams
only additive, which is wrong. We can't use values other than this symbol as it is possible that it may be valid user input.
- I'm concerned about the name as
DEFAULT_VALUE
implies more than we likely mean to convey. I'd nominateUNSET_VALUE
,UNBIND_VALUE
, orDELETE_VALUE
instead. - Can we stash this somewhere not on the
Ember
global?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also,
- This approach for unsetting does not completely support nested (structured) array query params. It's not easy to model
unset
behavior in this scenario:{ foo: [ "a", "b", "c"] }
=>foo[]=a&foo[]=b&foo[]=c
. That should instead do some sort of slice, so maybe some sort of:
{ foo: SLICE(SLICE(ORIGINAL,0,1),0,1) }
I believe that this needs to support nesting to be complete.
(This maps to my forthcoming serialization unification proposal.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This gets worse if you nest arrays of objects and want to UNSET a property on a nested value.
{
characters: [
{ thing: "one", hat: false },
{ thing: "two", hat: false },
{ thing: "cat", hat: true }
]
}
Indices get included because otherwise the array is ambiguous.
characters[0][thing]=one&characters[0][hat]=0&
characters[1][thing]=two&characters[1][hat]=0&
characters[2][thing]=cat&characters[2][hat]=1
If I wish to unset characters[2][hat]
and eliminate characters[0][thing]=one
(never liked that one anyway) I'm unsure it's possible to do gracefully with this set of proposed tools.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think structured query params are additive API, so unless there is something in this proposal that forecloses on your design, I think we don't need to completely solve it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They're additive in that they're new but considering our current state has severe issues I want to ensure that we feel good about our future direction.
For the basic scenario and only-keyed-objects I think we can get away with DELETE_VALUE
. To support arrays it almost has to become a Lisp where you specify the transforms. To do that gracefully I feel like we would need to adopt:
push
,pop
shift
,unshift
slice
splice
- ...
And that's beyond what I would want to support as it would come with a parser and all sorts of other annoying problems. Throwing away that proposal, I think it's bad.
We could always stamp something as "pre-merged" and expose an API to get the unsupplied
values. It'll make structured query param merging palatable for advanced users, doesn't impact this simple scenario, doesn't add a tremendous amount of complexity and another Lisp-y microsyntax.
For bonus points I don't think that needs to be specified at this time and seems like a direction where this doesn't paint us into any corners.
/FIN
I would like to thank everyone involved for shaping this RFC into its current state through detailed and constructive feedback. This service has been a long time coming, and we hope that transitioning from the current intimate API is both swift and easy. On top of that, it will unlock new capabilities and optimization opportunities. In this Final Comment Period only one new concern was raised, and it was considered that it is an additive concern that can be addressed by a follow up RFC to the present one. So all that's left to do is to merge it. 🎉 One final thank you to @ef4 for sticking with it for the last year and change! |
Would it make sense to consider cc: @kazuzenefits |
@MiguelMadero - I think it seems fine, but it should be done as a follow-up RFC. It should be small enough and be able to focus on just this one method... |
Thanks @rwjblue I can throw something together. I just wanted to check first. |
I think would be handy if we can expose methods like |
Not sure if this is the right place to add comments for continuing work on the router service or not (considering this was merged but doesn't represent the entirety of what was described in https://github.com/emberjs/rfcs/blob/master/text/0095-router-service.md). Anyway my 2c: What I would like to see is the addition of something like
I guess alternatively |
…4199) * [ember__routing] Add types from Router Service RFC emberjs/rfcs#95 * [ember__routing] Add `RouteInfo` tests Also includes tweaks to `RouteInfo` definition and docs
…4199) * [ember__routing] Add types from Router Service RFC emberjs/rfcs#95 * [ember__routing] Add `RouteInfo` tests Also includes tweaks to `RouteInfo` definition and docs
Rendered