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?

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
dc_Bita98
  • 851
  • 2
  • 17
  • 35

2 Answers2

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'));
ruth
  • 29,535
  • 4
  • 30
  • 57
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);
}
FredericBirke
  • 1,002
  • 1
  • 7
  • 13
  • 1
    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/`)? – dc_Bita98 Sep 01 '20 at 09:12
  • 1
    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. – FredericBirke Sep 01 '20 at 09:18
  • 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? – dc_Bita98 Sep 01 '20 at 09:35
  • 1
    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. – FredericBirke Sep 01 '20 at 09:49