How To Get Route Path Parameters In Non-Routed Angular Components 🦀

Tomas Trajan
Jun 4 · 9 min read
Now go, get those params like there is no tomorrow! (Original 📷 by Andras Kovacs)

In this article we’re going to explore…

Let’s go!

Angular routing is a very useful and powerful feature which usually “just works” as expected! However, there are some edge cases which deserves special attention…

Imagine a following situation. We have an Angular application with some route and this route defines a path parameter using :id in its route definition. The whole route path could look like the following /admin/user/:id.

Angular route definition with :id path param example

We can usually access this information in both sync and reactive ways in that particular component to which we navigated by injecting ActivatedRoute and calling this.id$ = this.route.paramMap.pipe(paramMap => paramMap.get('id')) or we can go with sync version and call this.id = this.route.snapshot.paramMap.get('id') which is even simpler, right?

Example of UserComponent retrieving :id path param from the ActivatedRoute in both reactive and sync ways

The choice between the Observable and sync snapshot retrieval of the path param depends on particular use case. We might need Observable if users can change url while application keeps displaying same component. In that case component has to keep reacting to the changes of the path parameter. This could for example mean that the component re-fetches data when the id in the url changes…

The Question

What if we wanted to know the value of the :id path parameter in a component which we didn’t route to?

The good example of such a component could be the AppComponent or LayoutComponent which usually represent root layout of the application and contain the first <router-outlet> tag which then displays other routed components…

This means that the AppComponent itself is displayed always and exists completely outside of the routing mechanism of the Angular application. Let’s call it (and other such components) the non-routed components.

As it turns out, getting route path parameter in a non-routed component is not easy at all!

Naive approach

First, we can try to do exactly the same as before and inject the ActivatedRoute into the AppComponent. The route will actually be injected and we can even access some pretty useful things like queryParams and fragment but unfortunately, the paramMap will be totally empty even though our url contains path parameter…

How is that possible, could it be just an issue with the timing or something similar?

After trying to make it work using various approaches like using timeout to try to access values of the paramMap later it became clear that this just doesn’t work!

As it turned out, all this trouble could have been prevented by simply looking into official Angular documentation as a first step of the solution finding process… Maybe I should make such posters and hang them on the wall😂

Read Angular official documentation, it’s good for you!


Even though it’s bit hard to find, documentation clearly states that:

ActivatedRoute is a a service that is provided to each route component that contains route specific information such as route parameters, static data, resolve data, global query params, and the global fragment.

There, it’s all spelled out nice and clear, ActivatedRoute provides path param info only to the corresponding route component!

Our AppComponent is a non-routed component and can only get direct access to global stuff like queryParams and fragment but not to the route specific path params…

OK, it’s not possible but how can we do it anyway?

There is “no such thing” ¹ as impossible in software engineering! Or at least when building “standard” data processing Angular applications 😉 So how can we make this work?

¹ besides some crazy algorithmic problems and probably many other things I never heard of 😅

The options

See? There is always a way! 😉

1. Move non-routed component

This solution can help us in case when the component in question does NOT represent some part of the base layout. For example, it can be a ContextComponent showing some contextual info about the current route.

In this case we can move this component into the template of every routed component instead of having it only once as a part of the base layout. That way we can inject ActivatedRoute and retrieve the path params without any issue!

The downside of this approach is that involves a bit of duplication. Every routed component will get its unique instance of the context component. This also means that any state has to be recreated from scratch on every navigation because the life-cycle of the ContextComponent will be now tied to the route itself!

🐤 Follow me on Twitter because you will get notified about new Angular blog posts and cool frontend stuff!😉

2. Implement workaround based on router navigation events

This solution is very powerful as it enables us to access path parameters of any route in any part of the application!

The price for it is that it’s bit harder to implement and a bit fragile so we have to pay extra attention to get it right…

The scenario we’re going to describe is about situation when we’re interested in the path parameter of the first level child route which would be defined relative to the base href of the application… { path: ':routePathParam' } which would be matched by url looking like /someParam.

First of all, our non-routed component has to inject Angular Router together with ActivatedRoute.

Now, if we tried to get path params in a standard way all we would get is a empty paramMap

Instead, what we have to do is to first define stream of Router navigationEnd events because we need to know when the routing was finished. After that we can try to access the route we’re interested in!

Once we have navigationEnd stream in place we can try to traverse the router state to find desired route beginning from the root and using firstChild property to descend deeper into the router state tree.

As we can see, we’re retrieving root route and accessing its firstChild property because we’re interested in the route defined with { path: ':routePathParam' } as shown above.

Let’s imagine we would like to access something like { path: 'user/:id' } the :id path param is now in the second level which means we would need to traverse deeper using this.route.root.firstChild.firstChild.

This is of course very brittle because it can easily happen that we’re navigating to the valid base route (/) which doesn’t have any nested routes. In that case the first firstChild will be undefined which will lead to an error of trying to access property of undefined when accessing firstChild.firstChild!

Making it more robust! 💎

As we discussed, there could be many valid router states which will lead to undefined routes just because we want to access state of deeply nested route and user currently navigated to a route which is nested less than the desired route…

In that case, we want to prevent any errors from occurring and return null instead as null correctly describes value of the route path parameter which doesn’t currently exist.

This is something we’re actually already doing in our first example but we have to extend it for multiple nesting levels with further checks…

In this example we’re checking if the desired sub-route is currently activated by checking for existence of firstChild and nested firstChild.firstChild.

If route exists, we proceed with getting value from the paramMap or return null .

Concerns

This solution can get even more complicated in cases when application uses secondary (auxiliary) routes. In such applications Angular router navigates to two or more routes simultaneously using a special Angular specific url syntax.

This is not a problem in itself but it will need additional logic to find correct ActivatedRoute when traversing router state from root. We might need to search in children instead of simply descending by using firstChild.


Check out the working StackBlitz example which demonstrates how to implement this in your own applications!

This example demonstrates how to access nested route path params from a non-routed Angular component

3. Create service which will hold the route path param state and make it available for all other interested components

Our last solution follows well known service based approach to the state sharing in Angular applications. The gist of it is that we will create a service which will be then injected to both routed and non-routed components.

The routed component will listen to the route path param changes and update service state accordingly. On the other hand the non-routed component will just subscribe to the path param state of the service… Ready?

Simple BehaviorSubject based state sharing Angular service

Our service has private BehaviorSubject that holds current state which is then exposed as public Observable to be subscribed by the interested components and services. The state is then updated by call to updatePathParamState method…

Routed component then has to subscribe to the paramMap of the ActivatedRoute, get desired param value and update the service state!

⚠️ ️Never forget to unsubscribe from any manual subscription to prevent nasty memory leaks and performance degradation!

The last piece is to listen to the service state in non-routed component to access desired path param value…

Consuming of the service state with | async pipe in the non-routed component template…

That’s it! We can now access route path param value in a non-routed component!

🌬️ Pffff, we’re done!😅

I hope you enjoyed this guide and learned one of the way how to access Angular route path params in a non-routed component!

Please support this article with your 👏👏👏 because it helps it to spread to a wider audience 🙏.

Also, don’t hesitate to ping me if you have any questions using the article responses or Twitter DMs @tomastrajan.

And never forget, future is bright

Obviously the bright future (📷 by Mohamed Thasneem)

Tomas Trajan

Written by

🅰️ Google Developer Expert for Angular #GDE ❤️ ️Typescript 🛠️ Maker of the @releasebutler and Medium Enhanced Stats 🌞 Obviously the bright Future