The Angular Router is the engine that transforms a standard Angular application into a Single Page Application (SPA). If you want a deeper look at why the SPA architecture is beneficial, you can check out this related article.
Our starting point is straightforward: defining a routing configuration. This initial setup maps specific URL paths to Angular components. A successful path match results in the associated component being rendered. Consider the following configuration:
This setup implies:
- Navigating to
/homerenders theHomecomponent. - Navigating to
/lessonsrenders theAllLessonscomponent. - Navigating anywhere else results in an error.
The obvious question then becomes: where exactly do these components appear in the view?
Establishing a primary router outlet
When a URL matches a route, the router looks for a router-outlet component in the template. This outlet serves as a dynamic container where the matched components, such as Home or AllLessons, are inserted. These are standard components and hold no special significance beyond being the targets of routing.
Bootstrapping the router
To complete the router setup, its directives and injectables need to be registered within the Angular bootstrap mechanism. This is achieved by importing the RouterModule into the root module of the application.
An important detail is using the forRoot method for configuration rather than adding the RouterModule directly. More insight into this requirement is available in this post on @NgModule.
With this foundation in place, accessing the /home or /lessons URLs should display the appropriate components. However, this is typically where newcomers start hitting a wall.
What could go wrong so soon?
With the current configuration, navigating via the router's internal mechanisms from the index page works without a hitch. Yet, if you manually type /lessons into the browser's address bar to access it directly, you are greeted with a 404 Page Not Found error. What causes this discrepancy?
Understanding the 404 Not Found navigation error
By default, the new router leverages the HTML5 History API. This means routing does not rely on the # portion of the URL, which is traditionally used to deep-link to a specific part of a page.
The
#(fragment) in a URL directs the browser to a particular section within the page. Modifying this fragment does NOT trigger a full page reload.
Consequently, when the router navigates to /lessons, that complete path appears in the address bar. This contrasts with the older approach, where the browser would show /#/lessons instead.
Why this problem does not occur using hash navigation
In the older strategy, the visible URL still pointed to the domain root. A refresh would therefore reload index.html, the single page application shell. The server ignores the fragment part of the URL, as it is only processed client-side.
With the HTML5 History API approach, however, the browser interprets /lessons as a request for a file named lessons from the server. Since no such file exists at that location, the server responds with a 404 Not Found error.
How to prevent the 404 not found issue?
To effectively use the HTML5 strategy, your server configuration must be adjusted. Any unmatched request needs to be redirected to index.html. So, a request to /lessons would serve the index.html file instead of returning a 404 error.
The specific implementation details depend on your server technology stack. For instance, if you are using Node.js with the Express framework, you would add a middleware function as the final item in your middleware chain to handle this fallback.
Getting this server configuration right from the outset is critical for a solid start with the router's HTML5 mode. Beyond that, virtually every application requires a default route or a catch-all fallback route.
Home and Fallback routes - why order matters
The component router supports the concept of both empty paths and wildcards. This allows you to define an index route and a fallback route in the following manner:
Here, the empty path maps the URL / to the Home component, while the wildcard path routes everything else to the PageNotFoundComponent. However, this configuration has a hidden catch.
Why order matters
A crucial aspect of route configuration is the significance of ordering. When an URL is presented to the router, it evaluates the configuration array sequentially, starting with the first element. If a match for the entire URL is found, the router halts its search and instantiates the corresponding components.
If you placed the fallback configuration (the ** wildcard) at the beginning of the array, every URL would immediately match that wildcard. This would effectively break all other routing logic, preventing any other route from being reached. Therefore, it's essential to list the fallback route as the final entry in the configuration array.
With this baseline setup established, let's turn our attention to navigation. There are two primary approaches to initiating route changes:
- Declarative template-based navigation using the
routerLinkdirective - Imperative or programmatic navigation using the
Routerservice API
Router Navigation with the routerLink directive
Since the RouterModule is included in our app, the routerLink directive is available for defining navigation links directly within templates. There are multiple ways to utilize it:
You might hardcode a string literal directly in the template, as shown for the home or courses routes. Alternatively, you can bind it to an expression. In that case, the expression should be an array of URL segments. For instance, to navigate to the /lessons path, you would pass an array containing the lessons string.
Programmatic router navigation
Alternatively, navigation can be triggered imperatively through the router's programmatic API. To do this, the router is injected into your component, after which you can invoke either the navigate or navigateByUrl methods.
A common need when moving between routes is passing parameters to the destination. This usually involves reading them on the target component's side.
Route Parameters - Avoid Memory Leaks
To retrieve parameters after navigating to a route, the best practice is to subscribe to the route parameters observable provided by the Router API. For instance, when navigating to a course detail component at /courses/1 (where 1 is the course ID), you can extract that ID directly from the URL.
The router's observable-based API enables components to listen for routing changes and respond accordingly. A key pitfall to avoid here is the introduction of memory leaks through unmanaged subscriptions. The lesson Exiting an Angular Route - How To Prevent Memory Leaks covers this in greater detail.
The notion of Route Snapshot and Router Snapshot
The component router is designed to be reactive, exposing observables for subscribing to and reacting to routing changes. However, there are times when you don't need the continuous stream of values; you might only require the values as they existed at the exact moment the component was instantiated. In these cases, you often want that data synchronously, rather than through an asynchronous subscription.
To accommodate this, the router provides the concept of a snapshot. You can inject either the route snapshot or a more general router snapshot directly into your routed component's constructor.
These snapshots give you immediate, synchronous access to the route parameters that were active during the navigation event.
Why do we a need a snapshot of the whole router?
While accessing the current route's snapshot is useful, there are scenarios, like parent-child route relationships, where you need a broader view. The router snapshot provides access to the state of the entire routing tree at that moment. This makes it easier to access parameters from parent or sibling routes without complex navigation between route objects.
This level of functionality is just one of the many advanced features on offer. The router also incorporates standard patterns like Child Routes, which are instrumental in building common user interface structures like a Master Detail view.
Implement Master Detail using Child Routes
We've actually been leveraging the concept of child routes implicitly already. When a route has multiple child routes defined, only one of those children can be active at any point in time.
This constraint is identical to our initial top-level configuration, where only /home or /lessons could be active simultaneously. By using the empty path feature and making the top-level route a componentless route specifically designed to hold children, we can refactor the earlier configuration as follows:
The resulting behavior remains exactly the same: the router ensures only a single component (Home for /home or AllLessons for /lessons) is displayed at a time. This is crucial because the configuration references a single router-outlet; the final matching process must yield exactly one component.
Remember, a componentless route is simply a route that takes part in URL path matching but never causes a component to be instantiated at that level.
Using Child Routes for implementing Master Detail
Child routes are particularly effective for implementing the Master Detail user interface pattern. We'll expand this concept further by creating a master route that supports multiple distinct types of detail views.
Consider an application with a course that has a list of lessons. When a user selects a lesson from that list, its details are displayed. To add complexity, let's assume there are several lesson types: video lessons, textual lectures, quizzes, and interactive exercises. The detail view for each type would logically be a different component.
Configuring child routes allows us to map each lesson type to its corresponding component, all nested under the same master route:
This nested structure demonstrates the flexibility of child routes, allowing for multiple levels of depth in your routing hierarchy.
Functionally, when the user clicks an item in the master list (the CourseLessons component), the router replaces it with the appropriate detail component based on the specific link clicked.
This master-detail scenario is the fundamental use case for child routes, a standard feature in many routing libraries. Another powerful, but often underutilized, feature is the use of auxiliary routes.
Auxiliary Routes: Definition and Purpose
So what exactly are auxiliary routes? In essence, they function just like standard routes, which are mapped to the router-outlet component as the primary route. The distinction lies in their target: auxiliary routes are associated with a separate outlet that requires a name, unlike the primary outlet which doesn't.
Consider a layout where multiple outlets exist, each backed by its own portion of the routing setup:
But how is this feasible if all matching relies on a single URL? That's a fair question.
Handling Multiple Outlets with One URL
The crucial insight for top-level auxiliary routes is that each one effectively has its own URL that starts at /. While auxiliary routes can be placed at lower levels, this discussion will center on the top-level configuration.
Picture splitting your browser viewport into several mini-browser windows, each having its own independent URL. You would then set up separate routing rules for each of these windows, enabling you to navigate them individually. Here are a few scenarios to illustrate this.
Common Applications for Auxiliary Routes
From the examples above, you can see distinct outlets correspond to different auxiliary routes. But what drives the decision to adopt them?
It's typical for apps to organize the screen into several distinct sections:
- a main navigation bar at the top
- a secondary sidebar that's usually a subset of the top menu
- a right-side panel that could show a list of lessons
- modal windows for editing a list item that should persist across route changes
- a chat panel that remains open while navigating
Illustrating an Auxiliary Route Configuration
Let's say we want to show a lesson playlist on the right side of the screen, and its content should change based on navigation—maybe showing recent lessons or those tied to a particular course:
In this setup, when the aside outlet's path is playlist, the Playlist component is rendered. This routing is set up independently from the primary route, operating on its own.
Now, let's examine how this actually functions and how a single URL can represent two different routes.
The URL Structure for Auxiliary Routes
The Angular Router has a special syntax that embeds auxiliary route URLs into the main URL string. Suppose we want to navigate so that the primary outlet displays AllLessons and the rightAside outlet shows Playlist. The resulting URL looks like this:
/lessons(aside:playlist)
Here, /lessons still directs the primary route to the AllLessons component. The portion inside the parentheses specifies an auxiliary route. It starts with the target outlet's name, which is aside.
Following that, there's a colon separator and then the URL for that outlet, which here is /playlist. This triggers the Playlist component to appear in the designated aside outlet.
It's worth noting that you can have more than one auxiliary route in the parentheses, using // as the delimiter. As an example, this format could define the URL for a left-menu outlet:
`/lessons(aside:playlist//leftmenu:/some/path)`
Wrapping Up
The Angular Router offers a wealth of powerful capabilities. Throughout this article, we've explored several core features with practical illustrations, including initial setup without common errors, how navigation is triggered, handling child routes, and working with auxiliary routes.
With just a few fundamental ideas, you can cater to a wide array of routing requirements.
Armed with a firm understanding of these basics, we can move on to a more intricate example in the next part of this series: Angular Router: A Complete Example (using Bootstrap)
We trust this guide helps you get off the ground with the Angular Router, and we hope you found it useful.
If you have any questions or feedback, please leave them in the comments section below, and we'll get back to you.
To stay informed about future posts regarding the Angular Router and other Angular subjects, we suggest subscribing to our newsletter:
For a deeper dive into the more sophisticated aspects of the Angular Router, we recommend exploring the Angular Router In Depth course, which covers the router in extensive detail.
If you're new to Angular, you might want to check out the Angular for Beginners Course:
More Articles on Angular
If you found this post valuable, here are some other well-received articles from 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
References
From the Victor Savkin blog (@victorsavkin):
Angular Router: Componentless routes, empty paths and redirects
