Angular 9 - parse each parameter for a given url

Viewed 3922

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

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'));

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);
}
Related