4

Suppose we have these kinds of urls:

[1] /home/users/:id

[2] /home/users/:id/posts/:id

[3] /home/users/:id/posts/:id/comments/:id

I want to make a method parseUrl(url: string): any[] {} which takes a url and returns an array containing each and every parameters of that url. So, parseUrl('/home/users/:id/posts/:id/comments/:id') will result in [userId, postId, commentId] How can I achieve that?

Naive approach: Suppose we know which url segments can contain parameters (in this case, users, posts and comments), we can split the url by '/' character, the check if the segment is equal users or posts or comments, and finally take the subsequent segment as url.

parseUrl(url: string): any[] {
    let params = new Array<any>();
    url.split('/')
      .filter((segment, index, urlSegments) => index > 0 && ['users', 'posts', 'comments'].includes(urlSegments[index - 1]))
      .forEach(segment => params.push(segment))
    return params;
}

Why this sucks? --> it's actually highly coupled to the structure of the url. If for example I had also a url like this: /home/users/new-post, then new-post would be deemed as a parameter.

Using ActivatedRoute or ActivatedRouteSnapshot: using the params property of *ActivatedRouteSnapshot` seems better because it's free from the structure of our url. The problem here is that in my particular case, I am able to retrieve the parameter of just the last url segment. So

parseUrl(route: ActivatedRouteSnapshot) {
    return route.params;
  }

will result in an object containing just for example {'id': 5} give /home/users/10/posts/10/comments/5 as current url. This is because (at least I think) I've configured lazy loaded module for users, posts and comments. So I have 3 routing module, one which matches the route users/:id, the second matches posts/:id and the third matches comments/:id. I've found ActivatedRouteSnapshot treating them as 3 separated url segments with one parameter each instead of a one single url with 3 parameters.

So in the end, is there a programmatic and general way to get each single parameter from an url in Anuglar 9?

2 Answers 2

2

You would need to recursively walk the router tree to collect all params. This snippet works only, if your paramKeys are unique, so

/home/users/:id/posts/:id/comments/:id would need to be /home/users/:userId/posts/:postId/comments/:commentId. If you would want to keep the old paramkey names, you would need to adapt the snippet accordingly.

It could look like this:

export parseUrl(route: ActivatedRouteSnapshot): Map<string,string> {
  const result = reduceRouterChildrenParams(route.root.firstChild, new Map<string, string>());
  return result;
}

reduceRouterChildrenParams(routerChild: ActivatedRouteSnapshot | null, data: Map<string, string>): RouteWalkerResult {
  if (!routerChild) {
      return data;
  }
  for (const paramMapKey of routerChild.paramMap.keys) {
    data.set(paramMapKey, routerChild.paramMap.get(paramMapKey)!);
  }
  return routerChild.children.reduce((previousValue, currentValue) => {
    return reduceRouterChildrenParams(currentValue, previousValue);
  }, data);
}
Sign up to request clarification or add additional context in comments.

4 Comments

First of all, thank you, it works perfectly. Even without changing parameters with unique names, since I can track url changes from router.event subscription and then call your function each time. Second, let me ask you one more thing: is root property of ActivatedRouteSnaphot relative to the current url segment (I mean, it represent the parent of the current url segment) or it always store the value of the first url segment matched by the router (so in this case, it will always be equal to /home, no matter if I access it when I'm in /home/users or /home/users/:id/posts/)?
Its always the most toplevel router, independent of lazy loaded modules or other stuff you can do with the routing, so it doesn't matter from which route (in you question e.G. /home/users or /home/users/:id/posts/, but it would also work with any totally different route) you access the method.
And the reason why it doesn't work with every parameter named as id is because the Map data type overrides the entries with the same name, isn't so?
Exactly, so you would either need to: 1. adapt you paramKey names, so that it works with a map, or 2. replace the map with something else or 3. you need to access the route segments name before the param and use that as a key for the map.
2

Instead of using "required" routing params, you could use optional params that let's you send objects over the route.

Try the following

Path configuration

{ path: '/home/users/', component: UsersComponent }

Calling the route

<a [routerLink]="['/home/users', { data: JSON.stringify([userId, postId, commentId]) }]">...</a>

or

this.router.navigate(['/home/users', { data: JSON.stringify([userId, postId, commentId]) }]);

Resulting URL

http://myapp.com/home/users;data=...

Retrieve the data

JSON.parse(this.router.snapshot.paramMap.get('data'));

1 Comment

This could be an idea, but I don't intend to modify my routing parameters to "not required"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.