-
Notifications
You must be signed in to change notification settings - Fork 24.8k
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
docs(router): Clearly document how to retrieve all params in the router tree #40306
Conversation
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.
As discussed recently in a meeting, we should try to avoid adding methods to classes, since they cannot be tree shaken. I would propose that this is made a free-standing helper function if possible. I think there is precedence here with things like convertToParamMap()
for instance.
Further, I wonder if it would be enough to provide something like
forEachChild<T>(root: ActivatedRouteSnapshot, cb: (snapshot: ActivatedRouteSnapshot) => T): T[];
which could then be used to walk the url tree, avoiding us having to implement lots of different similar helpers to this one?
I somehow missed that conversation about tree shaking class methods. I wanted to do this as a helper function in the first place, but was concerned with discoverability. I added documentation to the
I actually feel like that would be too generic and could be implemented by developers easily enough if needed. I added this one to specifically address #11023, which is one of the top upvoted issues for the router package and I haven't seen anything similar for other tree items ( |
f363823
to
e9df5d9
Compare
You can preview 6b47563 at https://pr40306-6b47563.ngbuilds.io/. |
You can preview e9df5d9 at https://pr40306-e9df5d9.ngbuilds.io/. |
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.
Reviewed-for: public-api
LGTM! Nice work @atscott .
ActivatedRouteSnapshot
to retrieve all …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.
LGTM 👍
Reviewed-for: public-api
Note to public API reviewers (@AndrewKushnir @IgorMinar @jelbourn @alxhub) after discussion with @petebacondarwin:
|
You can preview 80e20e6 at https://pr40306-80e20e6.ngbuilds.io/. |
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've read through your excellent comment at #11023 (comment) and other comments on that issue it seems that we can't agree on a best way to reconcile duplicates in the params tree. Several of the commenters used a simple depth-first approach ignoring siblings, but your implementation does take into account the siblings.
Additionally several of the users expect the collected params to be updated when child params are updated, so even with your helper you still need to write code that respond to the param changes.
It's a bit surprising that many developers write parent routes that need info about params coming from their child routes. It would be good to understand why this is the case before we jump into a solution in a form of a helper.
So my recommendation is not to add a helper until we clearly understand the requirements. In the meantime we should document in the API docs how to traverse the route tree and collect params or data.
(I've noticed the whole discussion about how this should work, after I reviewed the public api and left a few comments - I'll post those just as an fyi for future reference, but you can ignore them if we ultimately decide to go with a docs update instead.)
packages/router/src/router_state.ts
Outdated
* | ||
* @publicApi | ||
*/ | ||
export function getAllParamsInTree(activatedRouteSnapshot: ActivatedRouteSnapshot): Params { |
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.
can you please document the param and return types using jsdoc tags?
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.
Done.
packages/router/src/router_state.ts
Outdated
* | ||
* @publicApi | ||
*/ | ||
export function getAllParamsInTree(activatedRouteSnapshot: ActivatedRouteSnapshot): Params { |
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.
Rather than using Params
type, which is defined as [key: string]: any
, could you consider a more type-safe alternative? Could for example ParamsMap
be a suitable return type? If not, then [key: string]: string|undefined
or [key: string]: unknown
would be a type safe alternative.
I've also noticed that the strategy to resolve duplicates is "last dupe wins", which seems at odds with how ParamMap#get works. Is this intentional? If so, can you please clarify that in the docs where you talk about overriding.
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.
Updated the type to [key: string]: string|string[]
which I believe is the most accurate. I'm a bit torn on how I feel about it - I feel a bit like it should stay in-line with the type of snapshot.params
even if it isn't very type safe.
As for the duplicates, the "last dupe wins" is actually not truly at odds with the ParamMap#get
. The array there is in the context of actual array type query params or matrix params rather than duplicate parameter keys that may have appeared in the tree. The "last dupe wins" strategy is also used when inheriting parameters using paramsInheritanceStrategy: 'always'
(https://github.com/angular/angular/blob/master/packages/router/src/router_state.ts#L243).
As for documentation and testing the override behavior, I actually wanted to specifically leave out what the expectation should be. I meant it to be more of "this behavior is more or less undefined (even though it's predictable) - duplicate keys will overwrite each other." I could still document it though if you'd prefer. I'm fine going that direction as well and stating that users should provide their own implementation if they want different behavior, following our helper as an example.
imports: [RouterTestingModule.withRoutes([{ | ||
path: '', | ||
component: OutletCmp, | ||
children: [{ |
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 test case doesn't exercise the merging behavior. Could you add that please?
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.
See thread above about testing/documenting duplicate keys. I could still add a test for it if we want to concretely define the behavior.
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.
At the end of the day if this api has a sufficient usage, the implementation will become the spec, so if we do publish this as an api now or in the future, we should add a test at that point.
This seems to be moot at the moment because it seems that we are leaning towards just extending docs with a snippet of code for collecting params.
But in general, we can't rely on a behavior not being a public api just because we intentionally didn't document it, so for this reason it's usually good to have unit tests just to give us an early warning of a potential breaking change should we decide to change the behavior in the future.
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.
See comment in #40306 (review)
Reviewed-for: public-api
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.
@IgorMinar Thanks for the detailed review.
we can't agree on a best way to reconcile duplicates in the params tree.
Yea, I don't think this is really something that happens frequently or it's at least not something most users are concerned with. Note that the duplicates are handled in approximately the same way as paramsInheritanceStrategy
(the "approximate" part is that I've included named outlets, which might also have duplicate param keys).
Several of the commenters used a simple depth-first approach ignoring siblings, but your implementation does take into account the siblings.
Indeed, most of the comments there are using firstChild
only to traverse the tree. This would get only the params from the primary outlet and exclude named outlets. I don't know if this is really something that could possibly be agreed on and really depends on the end-user needs. Including the params from named outlets would pretty rarely affect the result. I don't necessarily think that they've been excluded in the comments deliberately - it's just that they almost never matter the params on named outlets are probably even less likely to matter for the use-cases that want all params from the root. But I do think it would be more correct to include them.
Additionally several of the users expect the collected params to be updated when child params are updated, so even with your helper you still need to write code that respond to the param changes.
I don't think this should be addressed in that way. The issue is really that people need something to subscribe to in order to get updated params. I don't think it matters if it's the router navigation events or the ActivatedRoute.params
, there just needs to be an easy, defined/documented way to do it.
It's a bit surprising that many developers write parent routes that need info about params coming from their child routes. It would be good to understand why this is the case before we jump into a solution in a form of a helper.
I don't think it's actually the "parent routes" that need child params but something more along the lines of this:
I have a url /webroot/account/:accountId/post/:postId
. I want to show the account name and/or post name in the floating header. This floating header would live on the root component, and there's currently no documented way to get params deep in the tree from a service/component higher up. It's sometimes difficult to get the data you want from a component that can be placed anywhere when the data is only available if the component is placed in a specific place.
So my recommendation is not to add a helper until we clearly understand the requirements. In the meantime we should document in the API docs how to traverse the route tree and collect params or data.
I'm starting to lean towards the documentation route rather than providing an importable implementation. Maybe a copy-paste-able implementation with a good description of expected behavior would be better, given that there isn't necessarily a 100% "correct" way to do this; the implementation really depends on what the end-user is looking for.
WDYT?
packages/router/src/router_state.ts
Outdated
* | ||
* @publicApi | ||
*/ | ||
export function getAllParamsInTree(activatedRouteSnapshot: ActivatedRouteSnapshot): Params { |
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.
Updated the type to [key: string]: string|string[]
which I believe is the most accurate. I'm a bit torn on how I feel about it - I feel a bit like it should stay in-line with the type of snapshot.params
even if it isn't very type safe.
As for the duplicates, the "last dupe wins" is actually not truly at odds with the ParamMap#get
. The array there is in the context of actual array type query params or matrix params rather than duplicate parameter keys that may have appeared in the tree. The "last dupe wins" strategy is also used when inheriting parameters using paramsInheritanceStrategy: 'always'
(https://github.com/angular/angular/blob/master/packages/router/src/router_state.ts#L243).
As for documentation and testing the override behavior, I actually wanted to specifically leave out what the expectation should be. I meant it to be more of "this behavior is more or less undefined (even though it's predictable) - duplicate keys will overwrite each other." I could still document it though if you'd prefer. I'm fine going that direction as well and stating that users should provide their own implementation if they want different behavior, following our helper as an example.
imports: [RouterTestingModule.withRoutes([{ | ||
path: '', | ||
component: OutletCmp, | ||
children: [{ |
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.
See thread above about testing/documenting duplicate keys. I could still add a test for it if we want to concretely define the behavior.
packages/router/src/router_state.ts
Outdated
* | ||
* @publicApi | ||
*/ | ||
export function getAllParamsInTree(activatedRouteSnapshot: ActivatedRouteSnapshot): Params { |
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.
Done.
You can preview ca0f417 at https://pr40306-ca0f417.ngbuilds.io/. |
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.
It's a bit surprising that many developers write parent routes that need info about params coming from their child routes. It would be good to understand why this is the case before we jump into a solution in a form of a helper.
I don't think it's actually the "parent routes" that need child params but something more along the lines of this:
I have a url /webroot/account/:accountId/post/:postId. I want to show the account name and/or post name in the floating header. This floating header would live on the root component, and there's currently no documented way to get params deep in the tree from a service/component higher up. It's sometimes difficult to get the data you want from a component that can be placed anywhere when the data is only available if the component is placed in a specific place.
I see, if you are trying to extract this from outside of the outlet tree then I can see how it could be puzzlingly difficult. If however the service depends on some occasionally present param value, then I would write the code in a way where the component that extracts the param, informs the global state of the new value.
In a hierarchical route config, the ownership of params is delegated to nested routes on purpose to enable decoupling. In the cases discussed it seems that this "local" state is being accessed from top-level context which while possible is not ideal.
So my recommendation is not to add a helper until we clearly understand the requirements. In the meantime we should document in the API docs how to traverse the route tree and collect params or data.
I'm starting to lean towards the documentation route rather than providing an importable implementation. Maybe a copy-paste-able implementation with a good description of expected behavior would be better, given that there isn't necessarily a 100% "correct" way to do this; the implementation really depends on what the end-user is looking for.
WDYT?
works for me.
imports: [RouterTestingModule.withRoutes([{ | ||
path: '', | ||
component: OutletCmp, | ||
children: [{ |
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.
At the end of the day if this api has a sufficient usage, the implementation will become the spec, so if we do publish this as an api now or in the future, we should add a test at that point.
This seems to be moot at the moment because it seems that we are leaning towards just extending docs with a snippet of code for collecting params.
But in general, we can't rely on a behavior not being a public api just because we intentionally didn't document it, so for this reason it's usually good to have unit tests just to give us an early warning of a potential breaking change should we decide to change the behavior in the future.
IMO saying we'd just add this to the docs is a bit hand-wavy. I think either approach here has a problem with respect to discoverability. Where would you see either approach (helper function or example) to fit into the bigger router docs? |
@jelbourn I was planning to put it directly on the |
8729f10
to
2cbfc90
Compare
@jelbourn @IgorMinar - I've made this a docs-only change as a fixup commit so it can be dropped (and the code change can be restored) if this is not a satisfactory solution. I feel like this is a happy medium and is easily discoverable - it'll show up on aio and in the hover info for the type. PTAL. I'll update the commit message if this version of the change is approved. |
You can preview 2cbfc90 at https://pr40306-2cbfc90.ngbuilds.io/. |
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.
LGTM, but it would be good to change the commit message/PR title to reflect the new nature of the change (I'd probably just squash the commits)
…er tree This commit documents how to add a helper function which combines all the params in the router state tree into a single object. It provides a starting point for developers to reference if they require a more fine-tuned approach. Fixes angular#11023
You can preview 2119dfe at https://pr40306-2119dfe.ngbuilds.io/. |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
This commit documents how to add a helper function which combines all the params
in the router state tree into a single object. It provides a starting point for
developers to reference if they require a more fine-tuned approach.
Fixes #11023