Most applications, even single page apps, have routing. Just like most things when it comes to code, there are several ways that routes can be implemented in Angular. In this post, we will look at ways to implement child routes and the pros and cons of each technique.
When creating navigation for an application we often need a route to a list of items, and then the ability to navigate to the item itself. Let's use a list of drink recipes for a coffee shop. Our routes could look something as follows:
In the above example (Listing 1) we have declared 2 routes, one to the home page with a path of /
and one to the recipes pages with a path of /list
. From there we have 2 options in how we structure our individual recipe routes.
Flat Routes
The first option is to stay at the same level of routing and add some slashes.
Notice the last route declared in Listing 2:
{ path: 'drinks/:drinkId': component: RecipeComponent },
We declare a parameter :drinkId
for each page that is going to be a recipe. However, our route is at the same level as the recipe list. The routes aren't nested in any way.
This architecture if diagrammed looks as follows (figure 1).
The downside to this technique is that is breaks relative paths inside of the application. Let's say we add 3rd level to our routing, which includes nutrition information for our drink (Listing 3).
To add a link back to the recipe from the nutrition page, we must be aware of which recipe we came from in order to jump back up a level.
The advantage of this technique however is in the ease of getting all of the parameters for the route. By subscribing to ActivatedRoute
params
or by looking at the params
in the ActivatedRoute
snapshot, all of the parameters will be listed. Note that unique names for parameters becomes important here, let's look at how this plays out below in Listing 5.
Whether we are subscribing to the ActivatedRoute
params
or looking at them from the snapshot, both the parameters are here and easily available.
Let's look a different way of creating our routes however, using child routes.
Child Routes
We will start again with our coffee shop example but this time instead of all of the routes being at the same hierarchical level, we will create child routes so that our hierarchy can not just be visual in the URL, such as the first example, but also programmatic (Listing 6).
The above code (Listing 6), diagrammed out produces the following tree (Figure 2).
We now have true hierarchy in our routes, so if we need to have a link back to the drink list from a particular drink recipe, we can now use relative paths without being sent directly back to root (Listing 7).
We can also use relative paths in our typescript using relativeTo
in our navigation method (Listing 8).
By adding { relativeTo: this.route }
in our navigate method we set what route the method needs to be relative to. In this case we are telling the method that the path should be relative to our current route.
The disadvantage to using child routes is that we don't get all the parameters right when we call the subscribe to the route params
on our current route (Listing 9).
We when subscribing to the routes parameters, we only get the drinkId
, we do not get the shopId
. We have 2 options go also get the shopId
.
- We can go up the route tree and by subscribing to the route's ancestor that includes the ID, look for the parameter value such as in Listing 10.
- Or we can set router configurations to include all parameters.
Version 1, going up the route tree to get the parameter from the ancestor is not ideal because it requires the component to understand where it lies in the route tree, and needs to be done for every parameter needed. Figure 10 shows how we would go about getting the shop ID.
For every parameter we want to get, we must go up the three and individually get the parameter value. Not ideal. This is where option 2 comes into play. In our module we can set our router configurations to include all params
in the route data as if we had flat routes. Listing 11 shows how it's done.
Where ever the RouterModule.forRoot()
is being called, usually in app.module
, we add a configuration object that contains paramsInheritanceStrategy: 'always'
in the forRoot
method. This makes ActivatedRoute
show current params
as well as ancestor route params
.
When on our drink recipe page, we now also get the shopId
by looking at the params
. We no longer need to go hunt through the ancestors to go find it.
Wrapping it up
We have reviewed two options when it comes to setting up our routes.
We can choose to have all of our routes at the same level. This is very easy to set up and does not require any extra configuration in order to get all of the parameters on the route. The downside to this approach is the lack of programmatic hierarchy which leads to breaking the ability to use relative paths.
With child routes, we gain the programmatic hierarchy which allow us to use relative paths both in the HTML and when using the navigate
method. The downside is that it requires more setup, both in the routes, and in the configurations if we want to easily get access to all of the params
including those on ancestor routes.
And of course, although I would not recommend, we can always mix the two strategies. Happy Coding!