In this post we are going to go on a guided tour about the new Angular reactive router. To avoid confusion, this post is about the newest 3.0 component router version and not
In this tour we are going to cover the following topics:
- Why aren't Single Page Apps everywhere today, and why that will likely change
- Initial Router Setup and configuration, avoiding a usual setup pitfall
- Setup a home route and a fallback route, learn why order matters
- Router navigation using routerLink
- Master Detail with Child Routes - how do Child Routes work?
- The notion of Route Snapshot and Router Snapshot
- Auxiliary Routes: what are they and when are they useful?
If you want to see how to build a full menu navigation system, have a look at this post Angular Router - How To Build a Navigation Menu with Bootstrap 4 and Nested Routes
Why aren't single page apps everywhere?
Single Page Applications have been around for many years now, but as we surf the Internet we rarely find them today. The main reason for that has been the difficulty of having single page apps be properly ranked by search engines.
This has kept for the longest time single page apps in the domain of authenticated dashboards and enterprise applications, where their benefits of usability and performance are usually less prioritized when compared to the public internet. Why is that?
Why are single page apps ideal for the public internet?
For authenticated apps like Gmail, we are usually OK to wait for 6 seconds for it to load multiple times a day. This is unlike the public internet, where performance and user experience are at a premium and one extra second (or even 200ms) of page reload has a significant cost.
Imagine a website like Amazon built as a single page application: if we could navigate between products without doing a full page reload that would represent a large reduction of page drop-offs.
Why will single page apps likely be more common in the future?
But there are some very good news for Angular developers, and developers of single page apps in general: the Google crawler is now much better at crawling single page applications.
This means single page apps are today more relevant than ever, and are very likely to become more common. Let's then learn how to build such type of applications using the Angular Router, starting at the beginning: the initial setup, where we are going to avoid a common pitfall right from the start.
Configuring the Angular Router
The first thing that we should do is simply write some routing configuration. What we are doing in that configuration is to map certain URL paths to Angular components: meaning that if there is path match then the component gets displayed.
The configuration is of type
RouteConfig, which is just an array of
Route configuration object. Let's configure a couple of simple routes:
This configuration simply means: if you navigate to
/home then the
Home component gets displayed; if you navigate to
AllLessons component gets displayed. And if you navigate elsewhere you are going to get an error.
But where do these components get displayed?
Configuring a primary router outlet
Once the router has a URL match, it will try to display the corresponding matching components. for that it ill look in the template for a
Router outlet is a dynamic component that the router uses to display in this case either the
AllLessons components. There is nothing special about these components, these could be any component.
Bootstrapping the router
To finish the setup of the router, we also need to add its directives and injectables into the Angular bootstrap system. We do so by adding by importing the
RouterModule into the application root module:
Notice that we configure the module by using the
forRoot function instead of simply adding the
RouterModule. To learn more about why this is necessary, have a look at this other post on @NgModule.
With this, if we access the
/lessons URLs, we would get the corresponding component displayed. But here is where for new users of the router sometimes things start to go wrong.
What could go wrong so soon?
With the current setup, if we navigate to an URL from the index page using the router built-in mechanisms, it all works. but if we try to type the URL in the browser address bar to access directly for example
/lessons, we get a 404 page not found error. Why is that?
This is something that it's better to solve from the very beginning, otherwise we will not have a good router development experience, and the application won't be usable in production. Let's go over the problem.
Understanding the problem
The first thing to know about the new router is that by default it uses the HTML5 History API. This means that routing is not based anymore on using the
# part of the URL, which is used to link directly to a section of the page. The
# is typically used for example to link directly to a section of a blog post.
This means that when the router navigates to
/lessons, that URL is really shown in the browser address bar. This is opposed to the ancient routing were the browser address bar would display
Why things used to work when using hash navigation
In the ancient strategy the URL was pointing to the root of the domain, so when hitting refresh this would reload
index.html, our single page application.
This is because the
# part of the URL was ignored by the server.
But now in the new strategy, this will cause an attempt to load a file called
lessons, which does not exist, and so we get 404 Not found.
How to prevent 404 not found in the new router?
In order to use the new HTLM5 strategy, you need to setup your server so that any unmatched request gets directed to
index.html, so that for example
/lessons gets as result
index.html and not 404 Not found.
The exact configuration will depend on which server technology is being used. Let's give an example, let's say we are using Node for the server and Express as the backend web framework.
To fix the 404 issue we would have to configure the following middleware as the last in the middleware chain:
This would give us a good start in using the new HTML5 mode of the router, without this the router usability is compromised. Another common source of issues is, how to configure a default or a fallback route, this is probably something that all applications need.
Home and Fallback routes - why order matters
The new component router supports the notions of empty paths and wildcards, meaning that we can configure an index route and a fallback route like this:
We can see that the empty path configuration would map the URL
/ to the component
Home, and all other paths to
PageNotFoundComponent. But there is a catch in this configuration as well.
Why order matters
One of the key things to know about routing configuration is that the order matters a lot. When the router receives an URL, it will start going through the configuration in order: from the first element of the configuration array.
If it finds a match to the complete URL, it stops and instantiates the corresponding component(s). So in this case, if we would put the fallback configuration in the beginning of the array, every URL will match to the
** wildcard and this break routing.
That's why we should put the fallback route configuration as the last entry in the array. With this baseline configuration, let's now set up some router navigation. There are two ways of doing this:
- declarative template based navigation with the
- programmatic or imperative navigation with the
Router Navigation with the routerLink directive
As we have included
RouterModule in our app, we can use the
routerLink directive to define router navigation links in our template. There are a couple of ways of doing this:
We can either hardcode a string directly in the template, like its the case of the home route or the courses route. But we can also pass it an expression. If so we need to pass it an array containing the multiple URL path parts that we want to navigate to: in this case we want to navigate to the
Programmatic router navigation
Another way of doing router navigation is to use the router programmatic API to do so. For that we just have to inject the router into our component, and make use of either the
navigateByUrl navigation methods:
One of the things that we usually want to do when navigating between two routes is to pass navigation parameters to the target route.
Route Parameters - Avoid Memory Leaks
If we want to read parameters between routes, we probably want to use the route parameters observable that the Router API exposes. For example, when navigating to the course detail component using
/courses/1 (1 being the course Id), we can recover the course Id from the URL:
As we can see the router provides observables that allow us to observe routing changes and react to them. One important pitfall to avoid with router navigation is to prevent memory leaks.
Have a look at the lesson Exiting an Angular Route - How To Prevent Memory Leaks for some more details.
The notion of Route Snapshot and Router Snapshot
One key notion about the new component router is that it's reactive. This means its API exposes several observables that can be subscribed to react to routing changes.
But sometimes we don't want the latest value of a route or its parameters, we just the values that were present only at the moment when the component was initially instantiated, and we usually want those values synchronously and not asynchronously.
For that, the reactive router introduces also the notion of snapshot. A snapshot can be injected in the constructor of the routed component:
With these snapshots we have access to the route parameters at the moment of navigation.
Why do we a need a snapshot of the whole router?
We can access the snapshot of the current route, but also of the whole router. The snapshot of the whole router would be useful for accessing for example route parameters of parent routes, like in the example above.
And this is just one of the many new features of the new router. The new router also implements the usual features of routing, such as Child routes which allow us to implement common UI patterns like Master Detail.
Implement Master Detail using Child Routes
Actually we were already using at least the notion of child routes without even knowing. When a route has multiple child routes, only one of those child routes can be active at any given time.
This sounds a lot like what was happening with our top-level routing configuration. Only
/lessons can be active at any given time. In fact, using the empty path routing feature and making the top-level route a componentless route, we can rewrite the configuration using child routes:
This gives the exact same result as before: only
/lessons can be active at one given time. Remember all this route config only has one
router-outlet, so the end result of the matching must be a single component and not multiple.
Also note that a componentless route is a route that participates in the path matching process but does not trigger the instantiation of a route component.
Using Child Routes for implementing Master Detail
One use case of child routes is to use them to implement the common UI pattern known as Master Detail. Actually we are going to go one step further and implement a master route with multiple types of detail routes.
Imagine a course with a list of lessons. You click on a lesson in the list in order to display it. But there is a catch, there are multiple types of lessons: video lessons, text lectures, quizzes or interactive exercises.
One way to configure this would be to use child routes:
This is one way to do it, we have gone here into several levels of nesting to show that it's possible.
The way this works is that when the user clicks in one of the lessons, depending on the link clicked a new detail screen will show replacing the master
This is the basis of child routes which is a common feature in many routers. Another common feature that is sometimes not used to its full potential are auxiliary routes.
Auxiliary Routes: what are they and when are they useful?
First, what are auxiliary routes? These are just plain routes like the primary route that was mapped to the
router-outlet component. But instead, auxiliary routes are mapped to a different outlet which must be named (unlike the primary outlet).
This is an example of a page with multiple outlets, each corresponding to a subset of routing configuration:
But how can this work, because all matching is done using the URL, and there is only one URL. Right ?
Multiple outlets, but only one URL ?
The key thing to realize about top-level auxiliary routes is that effectively each one has its own URL to match to, starting at
/. Auxiliary routes can also be configured not at the top-level, but let's focus on that scenario in this post.
Imagine that you divide your browser window into multiple mini-browser windows, each with its own separate URL. Then you provide separate routing configuration for each of those windows because you would want those windows to be navigated separately. Here are some examples.
Practical use cases of auxiliary routes
As you can see, different outlets correspond to different auxiliary routes. But when would you want to use auxiliary routes and why?
It's very common for applications to divide the page into multiple regions:
- the top-level menu
- the side menu that often is a subsection of the top menu
- an aside on the right maybe displaying a playlist of lessons
- popup dialogs for editing a list detail, that you want to keep upon during navigation
a chat window that stays opened during navigation
An example of an auxiliary route configuration
Imagine that to the right of our screen, we want to add a playlist of lessons that gets different content when we navigate: It might contain the list of latest lessons, or the lessons of a given course:
What we have configured here is that when the path of the
aside outlet is set to
playlist, we are going to display the component
Playlist. This routing can be defined independently of the primary route.
Let's see how does this work, how can the URL be used to display two URLs instead of one?
What does the URL look like for accessing an auxiliary route?
The Angular Router introduces a special syntax to allow to define auxiliary route URLs in the same URL as the primary route URL. Let's say that we want to trigger navigation and show
AllLessons in the primary outlet and
Playlist in the
rightAside outlet. The URL would look like this:
We can see that
/lessons would still route the primary route to the
AllLessons component. But inside parentheses we have an auxiliary route. First, we have the name of the outlet to which it refers to:
Then we have a colon separator and then we have the url that we want to apply to that outlet, in this case
/playlist. This would cause the
Playlist component to show in place of the
Note that you could have multiple auxiliary routes inside parenthesis, separated by
//. for example this would define the url for a left-menu outlet:
The new reactive Angular router is packed with super useful features. In this post we have gone over some of its core concepts with examples: initial setup while avoiding pitfalls, router navigation, child routes and auxiliary routes.
This new router was well worth the wait, it will give us a very solid foundation for building single page apps for years to come and its already much better than the previous version.
With improvements on search engines its possible that in the next few years single page apps are really taken to the mainstream of the web and deeply change our daily user experience. Looking forward to that !
Router Lessons available on YouTube
Do you like learning on YouTube? There are several router lessons on the playlist bellow, including router setup, child routes and auxiliary routes. Subscribe for more lessons, I hope you find the lessons useful:
Other posts on Angular
If you enjoyed this post, here some other popular posts in this blog:
- Angular Router - How To Build a Navigation Menu with Bootstrap 4 and Nested Routes
- Angular Components - The Fundamentals
- How to run Angular in Production Today
- How to build Angular apps using Observable Data Services - Pitfalls to avoid
- Introduction to Angular Forms - Template Driven, Model Driven or In-Between
- Angular ngFor - Learn all Features including trackBy, why is it not only for Arrays ?
- Angular Universal In Practice - How to build SEO Friendly Single Page Apps with Angular
From the Victor Savkin blog (@victorsavkin):