angular router tour

Angular Router Introduction: Child Routes, Auxiliary Routes, Master Detail

This is a comprehensive guide to the fundamental concepts of the Angular Router: routes, paths, components, outlets. We will cover both child and auxiliary routes, and we will learn how to setup the very commonly used Master Detail routing scenario.

Angular University

Angular University

High-Quality Angular Courses

More posts by Angular University.

In this post, we are going to do a guided tour of the main routing configuration concepts needed to use the Angular Router effectively.

The goal is to get a solid initial understanding of the Angular Router, before presenting a more advanced example. This post is the first of a series on the Angular Router, here is the complete series:

Angular Router Fundamentals: Child Routes, Auxiliary Routes, Master-Detail

Angular Router: A Complete Example (with Bootstrap)

Table Of Contents

In this post, we will cover the following topics:

  • 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, conclusions, angular router configuration: an introduction.

The Angular Router is what makes an Angular Application a Single Page Application, or SPA. For learning all about the benefits of the SPA architecture, have a look at this post .

The first thing that we should do is simply write some routing configuration. What we are doing in this initial configuration is to map certain URL paths to Angular components: meaning that if there is a path match then the component gets displayed:

This configuration means:

  • if you navigate to /home then the Home component gets displayed
  • if you navigate to /lessons then 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 will look in the template for a router-outlet component:

Router outlet is a dynamic component that the router uses to display in this case either the Home or 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 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 /home or /lessons URLs, we would get the corresponding component displayed.

But here is where for new users of the router things might 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?

Understanding the 404 Not Found navigation error

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 # part (known as the fragment) of the url is a link to a given section inside the page. Changing this does NOT cause a full page reload

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 /#/lessons instead.

Why this problem does not occur 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 # fragment in the URL is ignored by the server - this information is only used by the browser.

But now in the new HTML History API 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 the 404 not found issue?

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 setup would give us a good start for using the new HTML5 mode of the router, and it's important to get this right from the start.

Another thing that probably all applications need is configure a default or a fallback route.

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: starting with 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 routerLink directive
  • programmatic or imperative navigation with the Router API

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 /lessons path.

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 navigate or 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.

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 /home or /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 /home or /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 CourseLessons component.

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.

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:

/lessons(aside:playlist)

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: aside .

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 aside outlet.

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 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.

With just a handful of concepts, we can configure all sorts of different routing scenarios.

With a solid grasp on the router fundamentals, let's now go over a more advanced example in the next post of this series: Angular Router: A Complete Example (using Bootstrap)

I hope that this post helps with getting started with the Angular Router and that you enjoyed it!

If you have some questions or comments please let me know in the comments below and I will get back to you.

To get notified of upcoming posts on the Angular Router and other Angular topics, I invite you to subscribe to our newsletter:

And if you would like to learn a lot more about all the advanced features of the Angular Router, we recommend checking the Angular Router In Depth course, where the router is covered in much more detail.

If you are just getting started learning Angular, have a look at the Angular for Beginners Course :

angular router tour

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) :

Angular Router

Angular Router: Componentless routes, empty paths and redirects

Router tutorial: tour of heroes

This tutorial provides an extensive overview of the Angular router. In this tutorial, you build upon a basic router configuration to explore features such as child routes, route parameters, lazy load NgModules, guard routes, and preloading data to improve the user experience.

For a working example of the final version of the app, see the .

This guide describes development of a multi-page routed sample application. Along the way, it highlights key features of the router such as:

  • Organizing the application features into modules.
  • Navigating to a component ( Heroes link to "Heroes List").
  • Including a route parameter (passing the Hero id while routing to the "Hero Detail").
  • Child routes (the Crisis Center has its own routes).
  • The CanActivate guard (checking route access).
  • The CanActivateChild guard (checking child route access).
  • The CanDeactivate guard (ask permission to discard unsaved changes).
  • The Resolve guard (pre-fetching route data).
  • Lazy loading an NgModule .
  • The CanLoad guard (check before loading feature module assets).

This guide proceeds as a sequence of milestones as if you were building the application step-by-step, but assumes you are familiar with basic Angular concepts . For a general introduction to angular, see the Getting Started . For a more in-depth overview, see the Tour of Heroes tutorial.

Prerequisites

To complete this tutorial, you should have a basic understanding of the following concepts:

  • Angular CLI

You might find the Tour of Heroes tutorial helpful, but it is not required.

The sample application in action

The sample application for this tutorial helps the Hero Employment Agency find crises for heroes to solve.

The application has three main feature areas:

  • A Crisis Center for maintaining the list of crises for assignment to heroes.
  • A Heroes area for maintaining the list of heroes employed by the agency.
  • An Admin area to manage the list of crises and heroes.

Try it by clicking on this live example link .

The application renders with a row of navigation buttons and the Heroes view with its list of heroes.

Select one hero and the application takes you to a hero editing screen.

Alter the name. Click the "Back" button and the application returns to the heroes list which displays the changed hero name. Notice that the name change took effect immediately.

Had you clicked the browser's back button instead of the application's "Back" button, the application would have returned you to the heroes list as well. Angular application navigation updates the browser history as normal web navigation does.

Now click the Crisis Center link for a list of ongoing crises.

Select a crisis and the application takes you to a crisis editing screen. The Crisis Detail appears in a child component on the same page, beneath the list.

Alter the name of a crisis. Notice that the corresponding name in the crisis list does not change.

Unlike Hero Detail , which updates as you type, Crisis Detail changes are temporary until you either save or discard them by pressing the "Save" or "Cancel" buttons. Both buttons navigate back to the Crisis Center and its list of crises.

Click the browser back button or the "Heroes" link to activate a dialog.

You can say "OK" and lose your changes or click "Cancel" and continue editing.

Behind this behavior is the router's CanDeactivate guard. The guard gives you a chance to clean-up or ask the user's permission before navigating away from the current view.

The Admin and Login buttons illustrate other router capabilities covered later in the guide.

Milestone 1: Getting started

Begin with a basic version of the application that navigates between two empty views.

Generate a sample application with the Angular CLI.

Define Routes

A router must be configured with a list of route definitions.

Each definition translates to a Route object which has two things: a path , the URL path segment for this route; and a component , the component associated with this route.

The router draws upon its registry of definitions when the browser URL changes or when application code tells the router to navigate along a route path.

The first route does the following:

When the browser's location URL changes to match the path segment /crisis-center , then the router activates an instance of the CrisisListComponent and displays its view.

When the application requests navigation to the path /crisis-center , the router activates an instance of CrisisListComponent , displays its view, and updates the browser's address location and history with the URL for that path.

The first configuration defines an array of two routes with minimal paths leading to the CrisisListComponent and HeroListComponent .

Generate the CrisisList and HeroList components so that the router has something to render.

Replace the contents of each component with the following sample HTML.

Register Router and Routes

To use the Router , you must first register the RouterModule from the @angular/router package. Define an array of routes, appRoutes , and pass them to the RouterModule.forRoot() method. The RouterModule.forRoot() method returns a module that contains the configured Router service provider, plus other providers that the routing library requires. Once the application is bootstrapped, the Router performs the initial navigation based on the current browser URL.

Note: The RouterModule.forRoot() method is a pattern used to register application-wide providers. Read more about application-wide providers in the Singleton services guide.
Adding the configured RouterModule to the AppModule is sufficient for minimal route configurations. However, as the application grows, refactor the routing configuration into a separate file and create a Routing Module . A routing module is a special type of Service Module dedicated to routing.

Registering the RouterModule.forRoot() in the AppModule imports array makes the Router service available everywhere in the application.

Add the Router Outlet

The root AppComponent is the application shell. It has a title, a navigation bar with two links, and a router outlet where the router renders components.

The router outlet serves as a placeholder where the routed components are rendered.

The corresponding component template looks like this:

Define a Wildcard route

You've created two routes in the application so far, one to /crisis-center and the other to /heroes . Any other URL causes the router to throw an error and crash the app.

Add a wildcard route to intercept invalid URLs and handle them gracefully. A wildcard route has a path consisting of two asterisks. It matches every URL. Thus, the router selects this wildcard route if it can't match a route earlier in the configuration. A wildcard route can navigate to a custom "404 Not Found" component or redirect to an existing route.

The router selects the route with a first match wins strategy. Because a wildcard route is the least specific route, place it last in the route configuration.

To test this feature, add a button with a RouterLink to the HeroListComponent template and set the link to a non-existant route called "/sidekicks" .

The application fails if the user clicks that button because you haven't defined a "/sidekicks" route yet.

Instead of adding the "/sidekicks" route, define a wildcard route and have it navigate to a PageNotFoundComponent .

Create the PageNotFoundComponent to display when users visit invalid URLs.

Now when the user visits /sidekicks , or any other invalid URL, the browser displays "Page not found". The browser address bar continues to point to the invalid URL.

Set up redirects

When the application launches, the initial URL in the browser bar is by default:

That doesn't match any of the hard-coded routes which means the router falls through to the wildcard route and displays the PageNotFoundComponent .

The application needs a default route to a valid page. The default page for this application is the list of heroes. The application should navigate there as if the user clicked the "Heroes" link or pasted localhost:4200/heroes into the address bar.

Add a redirect route that translates the initial relative URL ( '' ) to the default path ( /heroes ) you want.

Add the default route somewhere above the wildcard route. It's just above the wildcard route in the following excerpt showing the complete appRoutes for this milestone.

The browser address bar shows .../heroes as if you'd navigated there directly.

A redirect route requires a pathMatch property to tell the router how to match a URL to the path of a route. In this app, the router should select the route to the HeroListComponent only when the entire URL matches '' , so set the pathMatch value to 'full' .

Technically, pathMatch = 'full' results in a route hit when the remaining , unmatched segments of the URL match '' . In this example, the redirect is in a top level route so the remaining URL and the entire URL are the same thing.

The other possible pathMatch value is 'prefix' which tells the router to match the redirect route when the remaining URL begins with the redirect route's prefix path. This doesn't apply to this sample application because if the pathMatch value were 'prefix' , every URL would match '' .

Try setting it to 'prefix' and clicking the Go to sidekicks button. Because that's a bad URL, you should see the "Page not found" page. Instead, you're still on the "Heroes" page. Enter a bad URL in the browser address bar. You're instantly re-routed to /heroes . Every URL, good or bad, that falls through to this route definition is a match.

The default route should redirect to the HeroListComponent only when the entire url is '' . Remember to restore the redirect to pathMatch = 'full' .

Learn more in Victor Savkin's post on redirects .

Milestone 1 wrap up

Your sample application can switch between two views when the user clicks a link.

Milestone 1 covered how to do the following:

  • Load the router library.
  • Add a nav bar to the shell template with anchor tags, routerLink and routerLinkActive directives.
  • Add a router-outlet to the shell template where views are displayed.
  • Configure the router module with RouterModule.forRoot() .
  • Set the router to compose HTML5 browser URLs.
  • Handle invalid routes with a wildcard route.
  • Navigate to the default route when the application launches with an empty path.

The starter application's structure looks like this:

Here are the files in this milestone.

Milestone 2: Routing module

This milestone shows you how to configure a special-purpose module called a Routing Module , which holds your application's routing configuration.

The Routing Module has several characteristics:

  • Separates routing concerns from other application concerns.
  • Provides a module to replace or remove when testing the application.
  • Provides a well-known location for routing service providers such as guards and resolvers.
  • Does not declare components.

Integrate routing with your app

The sample routing application does not include routing by default. When you use the Angular CLI to create a project that does use routing, set the --routing option for the project or application, and for each NgModule. When you create or initialize a new project (using the CLI ng new command) or a new application (using the ng generate app command), specify the --routing option. This tells the CLI to include the @angular/router npm package and create a file named app-routing.module.ts . You can then use routing in any NgModule that you add to the project or application.

For example, the following command generates an NgModule that can use routing.

This creates a separate file named my-module-routing.module.ts to store the NgModule's routes. The file includes an empty Routes object that you can fill with routes to different components and NgModules.

Refactor the routing configuration into a routing module

Create an AppRouting module in the /app folder to contain the routing configuration.

Import the CrisisListComponent , HeroListComponent , and PageNotFoundComponent symbols like you did in the app.module.ts . Then move the Router imports and routing configuration, including RouterModule.forRoot() , into this routing module.

Re-export the Angular RouterModule by adding it to the module exports array. By re-exporting the RouterModule here, the components declared in AppModule have access to router directives such as RouterLink and RouterOutlet .

After these steps, the file should look like this.

Next, update the app.module.ts file by removing RouterModule.forRoot in the imports array.

Later, this guide shows you how to create multiple routing modules and import those routing modules in the correct order .

The application continues to work just the same, and you can use AppRoutingModule as the central place to maintain future routing configuration.

Benefits of a routing module

The routing module, often called the AppRoutingModule , replaces the routing configuration in the root or feature module.

The routing module is helpful as your application grows and when the configuration includes specialized guard and resolver services.

Some developers skip the routing module when the configuration is minimal and merge the routing configuration directly into the companion module (for example, AppModule ).

Most applications should implement a routing module for consistency. It keeps the code clean when configuration becomes complex. It makes testing the feature module easier. Its existence calls attention to the fact that a module is routed. It is where developers expect to find and expand routing configuration.

Milestone 3: Heroes feature

This milestone covers the following:

  • Organizing the application and routes into feature areas using modules.
  • Navigating imperatively from one component to another.
  • Passing required and optional information in route parameters.

This sample application recreates the heroes feature in the "Services" section of the Tour of Heroes tutorial , and reuses much of the code from the .

A typical application has multiple feature areas, each dedicated to a particular business purpose with its own folder.

This section shows you how refactor the application into different feature modules, import them into the main module and navigate among them.

Add heroes functionality

Follow these steps:

  • To manage the heroes, create a HeroesModule with routing in the heroes folder and register it with the root AppModule .

Move the placeholder hero-list folder that's in the app folder into the heroes folder.

Copy the contents of the heroes/heroes.component.html from the "Services" tutorial into the hero-list.component.html template.

  • Re-label the <h2> to <h2>HEROES</h2> .
  • Delete the <app-hero-detail> component at the bottom of the template.

Copy the contents of the heroes/heroes.component.css from the live example into the hero-list.component.css file.

Copy the contents of the heroes/heroes.component.ts from the live example into the hero-list.component.ts file.

  • Change the component class name to HeroListComponent .
  • Change the selector to app-hero-list .
Selectors are not required for routed components because components are dynamically inserted when the page is rendered. However, they are useful for identifying and targeting them in your HTML element tree.
  • Copy the hero-detail folder, the hero.ts , hero.service.ts , and mock-heroes.ts files into the heroes subfolder.
  • Copy the message.service.ts into the src/app folder.
  • Update the relative path import to the message.service in the hero.service.ts file.

Next, update the HeroesModule metadata.

  • Import and add the HeroDetailComponent and HeroListComponent to the declarations array in the HeroesModule .

The hero management file structure is as follows:

Hero feature routing requirements

The heroes feature has two interacting components, the hero list and the hero detail. When you navigate to list view, it gets a list of heroes and displays them. When you click on a hero, the detail view has to display that particular hero.

You tell the detail view which hero to display by including the selected hero's ID in the route URL.

Import the hero components from their new locations in the src/app/heroes/ folder and define the two hero routes.

Now that you have routes for the Heroes module, register them with the Router using the RouterModule as you did in the AppRoutingModule , with an important difference.

In the AppRoutingModule , you used the static RouterModule.forRoot() method to register the routes and application level service providers. In a feature module you use the static forChild() method.

Only call RouterModule.forRoot() in the root AppRoutingModule (or the AppModule if that's where you register top level application routes). In any other module, you must call the RouterModule.forChild() method to register additional routes.

The updated HeroesRoutingModule looks like this:

Consider giving each feature module its own route configuration file. Though the feature routes are currently minimal, routes have a tendency to grow more complex even in small applications.

Remove duplicate hero routes

The hero routes are currently defined in two places: in the HeroesRoutingModule , by way of the HeroesModule , and in the AppRoutingModule .

Routes provided by feature modules are combined together into their imported module's routes by the router. This lets you continue defining the feature module routes without modifying the main route configuration.

Remove the HeroListComponent import and the /heroes route from the app-routing.module.ts .

Leave the default and the wildcard routes as these are still in use at the top level of the application.

Remove heroes declarations

Because the HeroesModule now provides the HeroListComponent , remove it from the AppModule 's declarations array. Now that you have a separate HeroesModule , you can evolve the hero feature with more components and different routes.

After these steps, the AppModule should look like this:

Module import order

Notice that in the module imports array, the AppRoutingModule is last and comes after the HeroesModule .

The order of route configuration is important because the router accepts the first route that matches a navigation request path.

When all routes were in one AppRoutingModule , you put the default and wildcard routes last, after the /heroes route, so that the router had a chance to match a URL to the /heroes route before hitting the wildcard route and navigating to "Page not found".

Each routing module augments the route configuration in the order of import. If you listed AppRoutingModule first, the wildcard route would be registered before the hero routes. The wildcard route—which matches every URL—would intercept the attempt to navigate to a hero route.

Reverse the routing modules to see a click of the heroes link resulting in "Page not found". Learn about inspecting the runtime router configuration below .

Route Parameters

Route definition with a parameter.

Return to the HeroesRoutingModule and look at the route definitions again. The route to HeroDetailComponent has an :id token in the path.

The :id token creates a slot in the path for a Route Parameter. In this case, this configuration causes the router to insert the id of a hero into that slot.

If you tell the router to navigate to the detail component and display "Magneta", you expect a hero ID to appear in the browser URL like this:

If a user enters that URL into the browser address bar, the router should recognize the pattern and go to the same "Magneta" detail view.

Embedding the route parameter token, :id , in the route definition path is a good choice for this scenario because the id is required by the HeroDetailComponent and because the value 15 in the path clearly distinguishes the route to "Magneta" from a route for some other hero.

Setting the route parameters in the list view

After navigating to the HeroDetailComponent , you expect to see the details of the selected hero. You need two pieces of information: the routing path to the component and the hero's id .

Accordingly, the link parameters array has two items: the routing path and a route parameter that specifies the id of the selected hero.

The router composes the destination URL from the array like this: localhost:4200/hero/15 .

The router extracts the route parameter ( id:15 ) from the URL and supplies it to the HeroDetailComponent using the ActivatedRoute service.

Activated Route in action

Import the Router , ActivatedRoute , and ParamMap tokens from the router package.

Import the switchMap operator because you need it later to process the Observable route parameters.

Add the services as private variables to the constructor so that Angular injects them (makes them visible to the component).

In the ngOnInit() method, use the ActivatedRoute service to retrieve the parameters for the route, pull the hero id from the parameters, and retrieve the hero to display.

When the map changes, paramMap gets the id parameter from the changed parameters.

Then you tell the HeroService to fetch the hero with that id and return the result of the HeroService request.

The switchMap operator does two things. It flattens the Observable<Hero> that HeroService returns and cancels previous pending requests. If the user re-navigates to this route with a new id while the HeroService is still retrieving the old id , switchMap discards that old request and returns the hero for the new id .

AsyncPipe handles the observable subscription and the component's hero property will be (re)set with the retrieved hero.

ParamMap API

The ParamMap API is inspired by the URLSearchParams interface . It provides methods to handle parameter access for both route parameters ( paramMap ) and query parameters ( queryParamMap ).

Observable paramMap and component reuse

In this example, you retrieve the route parameter map from an Observable . That implies that the route parameter map can change during the lifetime of this component.

By default, the router re-uses a component instance when it re-navigates to the same component type without visiting a different component first. The route parameters could change each time.

Suppose a parent component navigation bar had "forward" and "back" buttons that scrolled through the list of heroes. Each click navigated imperatively to the HeroDetailComponent with the next or previous id .

You wouldn't want the router to remove the current HeroDetailComponent instance from the DOM only to re-create it for the next id as this would re-render the view. For better UX, the router re-uses the same component instance and updates the parameter.

Because ngOnInit() is only called once per component instantiation, you can detect when the route parameters change from within the same instance using the observable paramMap property.

When subscribing to an observable in a component, you almost always unsubscribe when the component is destroyed. However, ActivatedRoute observables are among the exceptions because ActivatedRoute and its observables are insulated from the Router itself. The Router destroys a routed component when it is no longer needed. This means all the component's members will also be destroyed, including the injected ActivatedRoute and the subscriptions to its Observable properties. The Router does not complete any Observable of the ActivatedRoute so any finalize or complete blocks will not run. If you need to handle something in a finalize , you still need to unsubscribe in ngOnDestroy . You also have to unsubscribe if your observable pipe has a delay with code you do not want to run after the component is destroyed.

snapshot : the no-observable alternative

This application won't re-use the HeroDetailComponent . The user always returns to the hero list to select another hero to view. There's no way to navigate from one hero detail to another hero detail without visiting the list component in between. Therefore, the router creates a new HeroDetailComponent instance every time.

When you know for certain that a HeroDetailComponent instance will never be re-used, you can use snapshot .

route.snapshot provides the initial value of the route parameter map. You can access the parameters directly without subscribing or adding observable operators as in the following:

snapshot only gets the initial value of the parameter map with this technique. Use the observable paramMap approach if there's a possibility that the router could re-use the component. This tutorial sample application uses with the observable paramMap .

Navigating back to the list component

The HeroDetailComponent "Back" button uses the gotoHeroes() method that navigates imperatively back to the HeroListComponent .

The router navigate() method takes the same one-item link parameters array that you can bind to a [ routerLink ] directive. It holds the path to the HeroListComponent :

Route Parameters: Required or optional?

Use route parameters to specify a required parameter value within the route URL as you do when navigating to the HeroDetailComponent in order to view the hero with id 15:

You can also add optional information to a route request. For example, when returning to the hero-detail.component.ts list from the hero detail view, it would be nice if the viewed hero were preselected in the list.

You implement this feature by including the viewed hero's id in the URL as an optional parameter when returning from the HeroDetailComponent .

Optional information can also include other forms such as:

  • Loosely structured search criteria; for example, name='wind*' .
  • Multiple values; for example, after='12/31/2015' & before='1/1/2017' —in no particular order— before='1/1/2017' & after='12/31/2015' — in a variety of formats— during='currentYear' .

As these kinds of parameters don't fit smoothly in a URL path, you can use optional parameters for conveying arbitrarily complex information during navigation. Optional parameters aren't involved in pattern matching and afford flexibility of expression.

The router supports navigation with optional parameters as well as required route parameters. Define optional parameters in a separate object after you define the required route parameters.

In general, use a required route parameter when the value is mandatory (for example, if necessary to distinguish one route path from another); and an optional parameter when the value is optional, complex, and/or multivariate.

Heroes list: optionally selecting a hero

When navigating to the HeroDetailComponent you specified the required id of the hero-to-edit in the route parameter and made it the second item of the link parameters array .

The router embedded the id value in the navigation URL because you had defined it as a route parameter with an :id placeholder token in the route path :

When the user clicks the back button, the HeroDetailComponent constructs another link parameters array which it uses to navigate back to the HeroListComponent .

This array lacks a route parameter because previously you didn't need to send information to the HeroListComponent .

Now, send the id of the current hero with the navigation request so that the HeroListComponent can highlight that hero in its list.

Send the id with an object that contains an optional id parameter. For demonstration purposes, there's an extra junk parameter ( foo ) in the object that the HeroListComponent should ignore. Here's the revised navigation statement:

The application still works. Clicking "back" returns to the hero list view.

Look at the browser address bar.

It should look something like this, depending on where you run it:

The id value appears in the URL as ( ;id=15;foo=foo ), not in the URL path. The path for the "Heroes" route doesn't have an :id token.

The optional route parameters are not separated by "?" and "&" as they would be in the URL query string. They are separated by semicolons ";". This is matrix URL notation.

Matrix URL notation is an idea first introduced in a 1996 proposal by the founder of the web, Tim Berners-Lee. Although matrix notation never made it into the HTML standard, it is legal and it became popular among browser routing systems as a way to isolate parameters belonging to parent and child routes. As such, the Router provides support for the matrix notation across browsers.

Route parameters in the ActivatedRoute service

In its current state of development, the list of heroes is unchanged. No hero row is highlighted.

The HeroListComponent needs code that expects parameters.

Previously, when navigating from the HeroListComponent to the HeroDetailComponent , you subscribed to the route parameter map Observable and made it available to the HeroDetailComponent in the ActivatedRoute service. You injected that service in the constructor of the HeroDetailComponent .

This time you'll be navigating in the opposite direction, from the HeroDetailComponent to the HeroListComponent .

First, extend the router import statement to include the ActivatedRoute service symbol:

Import the switchMap operator to perform an operation on the Observable of route parameter map.

Inject the ActivatedRoute in the HeroListComponent constructor.

The ActivatedRoute.paramMap property is an Observable map of route parameters. The paramMap emits a new map of values that includes id when the user navigates to the component. In ngOnInit() you subscribe to those values, set the selectedId , and get the heroes.

Update the template with a class binding . The binding adds the selected CSS class when the comparison returns true and removes it when false . Look for it within the repeated <li> tag as shown here:

Add some styles to apply when the hero is selected.

When the user navigates from the heroes list to the "Magneta" hero and back, "Magneta" appears selected:

The optional foo route parameter is harmless and the router continues to ignore it.

Adding routable animations

This section shows you how to add some animations to the HeroDetailComponent .

First, import the BrowserAnimationsModule and add it to the imports array:

Next, add a data object to the routes for HeroListComponent and HeroDetailComponent . Transitions are based on states and you use the animation data from the route to provide a named animation state for the transitions.

Create an animations.ts file in the root src/app/ folder. The contents look like this:

This file does the following:

Imports the animation symbols that build the animation triggers, control state, and manage transitions between states.

Exports a constant named slideInAnimation set to an animation trigger named routeAnimation .

Defines one transition when switching back and forth from the heroes and hero routes to ease the component in from the left of the screen as it enters the application view ( :enter ), the other to animate the component to the right as it leaves the application view ( :leave ).

Back in the AppComponent , import the RouterOutlet token from the @angular/router package and the slideInAnimation from './animations.ts .

Add an animations array to the @ Component metadata that contains the slideInAnimation .

To use the routable animations, wrap the RouterOutlet inside an element, use the @routeAnimation trigger, and bind it to the element.

For the @routeAnimation transitions to key off states, provide it with the data from the ActivatedRoute . The RouterOutlet is exposed as an outlet template variable, so you bind a reference to the router outlet. This example uses a variable of routerOutlet .

The @routeAnimation property is bound to the getAnimationData() with the provided routerOutlet reference, so the next step is to define that function in the AppComponent . The getAnimationData() function returns the animation property from the data provided through the ActivatedRoute . The animation property matches the transition names you used in the slideInAnimation defined in animations.ts .

When switching between the two routes, the HeroDetailComponent and HeroListComponent now ease in from the left when routed to, and slide to the right when navigating away.

Milestone 3 wrap up

This section covered the following:

  • Organizing the application into feature areas.
  • Passing information along in route parameters and subscribe to them in the component.
  • Importing the feature area NgModule into the AppModule .
  • Applying routable animations based on the page.

After these changes, the folder structure is as follows:

Here are the relevant files for this version of the sample application.

Milestone 4: Crisis center feature

This section shows you how to add child routes and use relative routing in your app.

To add more features to the application's current crisis center, take similar steps as for the heroes feature:

  • Create a crisis-center subfolder in the src/app folder.
  • Copy the files and folders from app/heroes into the new crisis-center folder.
  • In the new files, change every mention of "hero" to "crisis", and "heroes" to "crises".
  • Rename the NgModule files to crisis-center.module.ts and crisis-center-routing.module.ts .

Use mock crises instead of mock heroes:

The resulting crisis center is a foundation for introducing a new concept—child routing. You can leave Heroes in its current state as a contrast with the Crisis Center.

In keeping with the Separation of Concerns principle , changes to the Crisis Center don't affect the AppModule or any other feature's component.

A crisis center with child routes

This section shows you how to organize the crisis center to conform to the following recommended pattern for Angular applications:

  • Each feature area resides in its own folder.
  • Each feature has its own Angular feature module.
  • Each area has its own area root component.
  • Each area root component has its own router outlet and child routes.
  • Feature area routes rarely (if ever) cross with routes of other features.

If your application had many feature areas, the component trees might consist of multiple components for those features, each with branches of other, related, components.

Child routing component

Generate a CrisisCenter component in the crisis-center folder:

Update the component template with the following markup:

The CrisisCenterComponent has the following in common with the AppComponent :

  • It is the root of the crisis center area, just as AppComponent is the root of the entire application.
  • It is a shell for the crisis management feature area, just as the AppComponent is a shell to manage the high-level workflow.

Like most shells, the CrisisCenterComponent class is minimal because it has no business logic, and its template has no links, just a title and < router-outlet > for the crisis center child component.

Child route configuration

As a host page for the "Crisis Center" feature, generate a CrisisCenterHome component in the crisis-center folder.

Update the template with a welcome message to the Crisis Center .

Update the crisis-center-routing.module.ts you renamed after copying it from heroes-routing.module.ts file. This time, you define child routes within the parent crisis-center route.

Notice that the parent crisis-center route has a children property with a single route containing the CrisisListComponent . The CrisisListComponent route also has a children array with two routes.

These two routes navigate to the crisis center child components, CrisisCenterHomeComponent and CrisisDetailComponent , respectively.

There are important differences in the way the router treats child routes.

The router displays the components of these routes in the RouterOutlet of the CrisisCenterComponent , not in the RouterOutlet of the AppComponent shell.

The CrisisListComponent contains the crisis list and a RouterOutlet to display the Crisis Center Home and Crisis Detail route components.

The Crisis Detail route is a child of the Crisis List . The router reuses components by default, so the Crisis Detail component is re-used as you select different crises. In contrast, back in the Hero Detail route, the component was recreated each time you selected a different hero from the list of heroes.

At the top level, paths that begin with / refer to the root of the application. But child routes extend the path of the parent route. With each step down the route tree, you add a slash followed by the route path, unless the path is empty.

Apply that logic to navigation within the crisis center for which the parent path is /crisis-center .

To navigate to the CrisisCenterHomeComponent , the full URL is /crisis-center ( /crisis-center + '' + '' ).

To navigate to the CrisisDetailComponent for a crisis with id=2 , the full URL is /crisis-center/2 ( /crisis-center + '' + '/2' ).

The absolute URL for the latter example, including the localhost origin, is as follows:

Here's the complete crisis-center-routing.module.ts file with its imports.

Import crisis center module into the AppModule routes

As with the HeroesModule , you must add the CrisisCenterModule to the imports array of the AppModule before the AppRoutingModule :

The import order of the modules is important because the order of the routes defined in the modules affects route matching. If the `AppModule` were imported first, its wildcard route (`path: '**'`) would take precedence over the routes defined in `CrisisCenterModule`. For more information, see the section on [route order](guide/router#route-order).

Remove the initial crisis center route from the app-routing.module.ts because now the HeroesModule and the CrisisCenter modules provide the feature routes.

The app-routing.module.ts file retains the top-level application routes such as the default and wildcard routes.

Relative navigation

While building out the crisis center feature, you navigated to the crisis detail route using an absolute path that begins with a slash.

The router matches such absolute paths to routes starting from the top of the route configuration.

You could continue to use absolute paths like this to navigate inside the Crisis Center feature, but that pins the links to the parent routing structure. If you changed the parent /crisis-center path, you would have to change the link parameters array.

You can free the links from this dependency by defining paths that are relative to the current URL segment. Navigation within the feature area remains intact even if you change the parent route path to the feature.

The router supports directory-like syntax in a link parameters list to help guide route name lookup: ./ or no leading slash is relative to the current level. ../ to go up one level in the route path. You can combine relative navigation syntax with an ancestor path. If you must navigate to a sibling route, you could use the ../<sibling> convention to go up one level, then over and down the sibling route path.

To navigate a relative path with the Router.navigate method, you must supply the ActivatedRoute to give the router knowledge of where you are in the current route tree.

After the link parameters array , add an object with a relativeTo property set to the ActivatedRoute . The router then calculates the target URL based on the active route's location.

Always specify the complete absolute path when calling router's navigateByUrl() method.

Navigate to crisis list with a relative URL

You've already injected the ActivatedRoute that you need to compose the relative navigation path.

When using a RouterLink to navigate instead of the Router service, you'd use the same link parameters array, but you wouldn't provide the object with the relativeTo property. The ActivatedRoute is implicit in a RouterLink directive.

Update the gotoCrises() method of the CrisisDetailComponent to navigate back to the Crisis Center list using relative path navigation.

Notice that the path goes up a level using the ../ syntax. If the current crisis id is 3 , the resulting path back to the crisis list is /crisis-center/;id=3;foo=foo .

Displaying multiple routes in named outlets

You decide to give users a way to contact the crisis center. When a user clicks a "Contact" button, you want to display a message in a popup view.

The popup should stay open, even when switching between pages in the application, until the user closes it by sending the message or canceling. Clearly you can't put the popup in the same outlet as the other pages.

Until now, you've defined a single outlet and you've nested child routes under that outlet to group routes together. The router only supports one primary unnamed outlet per template.

A template can also have any number of named outlets. Each named outlet has its own set of routes with their own components. Multiple outlets can display different content, determined by different routes, all at the same time.

Add an outlet named "popup" in the AppComponent , directly following the unnamed outlet.

That's where a popup goes, once you learn how to route a popup component to it.

Secondary routes

Named outlets are the targets of secondary routes .

Secondary routes look like primary routes and you configure them the same way. They differ in a few key respects.

  • They are independent of each other.
  • They work in combination with other routes.
  • They are displayed in named outlets.

Generate a new component to compose the message.

It displays a short form with a header, an input box for the message, and two buttons, "Send" and "Cancel".

Here's the component, its template, and styles:

It looks similar to any other component in this guide, but there are two key differences.

Note that the send() method simulates latency by waiting a second before "sending" the message and closing the popup.

The closePopup() method closes the popup view by navigating to the popup outlet with a null which the section on clearing secondary routes covers.

Add a secondary route

Open the AppRoutingModule and add a new compose route to the appRoutes .

In addition to the path and component properties, there's a new property called outlet , which is set to 'popup' . This route now targets the popup outlet and the ComposeMessageComponent will display there.

To give users a way to open the popup, add a "Contact" link to the AppComponent template.

Although the compose route is configured to the "popup" outlet, that's not sufficient for connecting the route to a RouterLink directive. You have to specify the named outlet in a link parameters array and bind it to the RouterLink with a property binding.

The link parameters array contains an object with a single outlets property whose value is another object keyed by one (or more) outlet names. In this case there is only the "popup" outlet property and its value is another link parameters array that specifies the compose route.

In other words, when the user clicks this link, the router displays the component associated with the compose route in the popup outlet.

This outlets object within an outer object was unnecessary when there was only one route and one unnamed outlet. The router assumed that your route specification targeted the unnamed primary outlet and created these objects for you. Routing to a named outlet revealed a router feature: you can target multiple outlets with multiple routes in the same RouterLink directive.

Secondary route navigation: merging routes during navigation

Navigate to the Crisis Center and click "Contact". you should see something like the following URL in the browser address bar.

The relevant part of the URL follows the ... :

  • The crisis-center is the primary navigation.
  • Parentheses surround the secondary route.
  • The secondary route consists of an outlet name ( popup ), a colon separator, and the secondary route path ( compose ).

Click the Heroes link and look at the URL again.

The primary navigation part changed; the secondary route is the same.

The router is keeping track of two separate branches in a navigation tree and generating a representation of that tree in the URL.

You can add many more outlets and routes, at the top level and in nested levels, creating a navigation tree with many branches and the router will generate the URLs to go with it.

You can tell the router to navigate an entire tree at once by filling out the outlets object and then pass that object inside a link parameters array to the router.navigate method.

Clearing secondary routes

Like regular outlets, secondary outlets persists until you navigate away to a new component.

Each secondary outlet has its own navigation, independent of the navigation driving the primary outlet. Changing a current route that displays in the primary outlet has no effect on the popup outlet. That's why the popup stays visible as you navigate among the crises and heroes.

The closePopup() method again:

Clicking the "send" or "cancel" buttons clears the popup view. The closePopup() function navigates imperatively with the Router.navigate() method, passing in a link parameters array .

Like the array bound to the Contact RouterLink in the AppComponent , this one includes an object with an outlets property. The outlets property value is another object with outlet names for keys. The only named outlet is 'popup' .

This time, the value of 'popup' is null . That's not a route, but it is a legitimate value. Setting the popup RouterOutlet to null clears the outlet and removes the secondary popup route from the current URL.

Milestone 5: Route guards

At the moment, any user can navigate anywhere in the application any time, but sometimes you need to control access to different parts of your application for various reasons, some of which might include the following:

  • Perhaps the user is not authorized to navigate to the target component.
  • Maybe the user must login (authenticate) first.
  • Maybe you should fetch some data before you display the target component.
  • You might want to save pending changes before leaving a component.
  • You might ask the user if it's OK to discard pending changes rather than save them.

You add guards to the route configuration to handle these scenarios.

A guard's return value controls the router's behavior:

  • If it returns true , the navigation process continues.
  • If it returns false , the navigation process stops and the user stays put.
  • If it returns a UrlTree , the current navigation cancels and a new navigation is initiated to the UrlTree returned.
Note: The guard can also tell the router to navigate elsewhere, effectively canceling the current navigation. When doing so inside a guard, the guard should return false ;

The guard might return its boolean answer synchronously. But in many cases, the guard can't produce an answer synchronously. The guard could ask the user a question, save changes to the server, or fetch fresh data. These are all asynchronous operations.

Accordingly, a routing guard can return an Observable<boolean> or a Promise<boolean> and the router will wait for the observable to resolve to true or false .

Note: The observable provided to the Router must also complete. If the observable does not complete, the navigation does not continue.

The router supports multiple guard interfaces:

CanActivate to mediate navigation to a route.

CanActivateChild to mediate navigation to a child route.

CanDeactivate to mediate navigation away from the current route.

Resolve to perform route data retrieval before route activation.

CanLoad to mediate navigation to a feature module loaded asynchronously .

You can have multiple guards at every level of a routing hierarchy. The router checks the CanDeactivate guards first, from the deepest child route to the top. Then it checks the CanActivate and CanActivateChild guards from the top down to the deepest child route. If the feature module is loaded asynchronously, the CanLoad guard is checked before the module is loaded. If any guard returns false, pending guards that have not completed are canceled, and the entire navigation is canceled.

There are several examples over the next few sections.

CanActivate : requiring authentication

Applications often restrict access to a feature area based on who the user is. You could permit access only to authenticated users or to users with a specific role. You might block or limit access until the user's account is activated.

The CanActivate guard is the tool to manage these navigation business rules.

Add an admin feature module

This section guides you through extending the crisis center with some new administrative features. Start by adding a new feature module named AdminModule .

Generate an admin folder with a feature module file and a routing configuration file.

Next, generate the supporting components.

The admin feature file structure looks like this:

The admin feature module contains the AdminComponent used for routing within the feature module, a dashboard route and two unfinished components to manage crises and heroes.

Although the admin dashboard RouterLink only contains a relative slash without an additional URL segment, it is a match to any route within the admin feature area. You only want the Dashboard link to be active when the user visits that route. Adding an additional binding to the Dashboard routerLink, [routerLinkActiveOptions]="{ exact: true }" , marks the ./ link as active when the user navigates to the /admin URL and not when navigating to any of the child routes.

Component-less route: grouping routes without a component

The initial admin routing configuration:

The child route under the AdminComponent has a path and a children property but it's not using a component . This defines a component-less route.

To group the Crisis Center management routes under the admin path a component is unnecessary. Additionally, a component-less route makes it easier to guard child routes .

Next, import the AdminModule into app.module.ts and add it to the imports array to register the admin routes.

Add an "Admin" link to the AppComponent shell so that users can get to this feature.

Guard the admin feature

Currently, every route within the Crisis Center is open to everyone. The new admin feature should be accessible only to authenticated users.

Write a canActivate() guard method to redirect anonymous users to the login page when they try to enter the admin area.

Generate an AuthGuard in the auth folder.

To demonstrate the fundamentals, this example only logs to the console, returns true immediately, and lets navigation proceed:

Next, open admin-routing.module.ts , import the AuthGuard class, and update the admin route with a canActivate guard property that references it:

The admin feature is now protected by the guard, but the guard requires more customization to work fully.

Authenticate with AuthGuard

Make the AuthGuard mimic authentication.

The AuthGuard should call an application service that can login a user and retain information about the current user. Generate a new AuthService in the auth folder:

Update the AuthService to log in the user:

Although it doesn't actually log in, it has an isLoggedIn flag to tell you whether the user is authenticated. Its login() method simulates an API call to an external service by returning an observable that resolves successfully after a short pause. The redirectUrl property stores the URL that the user wanted to access so you can navigate to it after authentication.

To keep things minimal, this example redirects unauthenticated users to /admin .

Revise the AuthGuard to call the AuthService .

Notice that you inject the AuthService and the Router in the constructor. You haven't provided the AuthService yet but it's good to know that you can inject helpful services into routing guards.

This guard returns a synchronous boolean result. If the user is logged in, it returns true and the navigation continues.

The ActivatedRouteSnapshot contains the future route that will be activated and the RouterStateSnapshot contains the future RouterState of the application, should you pass through the guard check.

If the user is not logged in, you store the attempted URL the user came from using the RouterStateSnapshot.url and tell the router to redirect to a login page—a page you haven't created yet. Returning a UrlTree tells the Router to cancel the current navigation and schedule a new one to redirect the user.

Add the LoginComponent

You need a LoginComponent for the user to log in to the application. After logging in, you'll redirect to the stored URL if available, or use the default URL. There is nothing new about this component or the way you use it in the router configuration.

Register a /login route in the auth/auth-routing.module.ts . In app.module.ts , import and add the AuthModule to the AppModule imports.

CanActivateChild : guarding child routes

You can also protect child routes with the CanActivateChild guard. The CanActivateChild guard is similar to the CanActivate guard. The key difference is that it runs before any child route is activated.

You protected the admin feature module from unauthorized access. You should also protect child routes within the feature module.

Extend the AuthGuard to protect when navigating between the admin routes. Open auth.guard.ts and add the CanActivateChild interface to the imported tokens from the router package.

Next, implement the canActivateChild() method which takes the same arguments as the canActivate() method: an ActivatedRouteSnapshot and RouterStateSnapshot . The canActivateChild() method can return an Observable<boolean| UrlTree > or Promise<boolean| UrlTree > for async checks and a boolean or UrlTree for sync checks. This one returns either true to let the user access the admin feature module or UrlTree to redirect the user to the login page instead:

Add the same AuthGuard to the component-less admin route to protect all other child routes at one time instead of adding the AuthGuard to each route individually.

CanDeactivate : handling unsaved changes

Back in the "Heroes" workflow, the application accepts every change to a hero immediately without validation.

In the real world, you might have to accumulate the users changes, validate across fields, validate on the server, or hold changes in a pending state until the user confirms them as a group or cancels and reverts all changes.

When the user navigates away, you can let the user decide what to do with unsaved changes. If the user cancels, you'll stay put and allow more changes. If the user approves, the application can save.

You still might delay navigation until the save succeeds. If you let the user move to the next screen immediately and saving were to fail (perhaps the data is ruled invalid), you would lose the context of the error.

You need to stop the navigation while you wait, asynchronously, for the server to return with its answer.

The CanDeactivate guard helps you decide what to do with unsaved changes and how to proceed.

Cancel and save

Users update crisis information in the CrisisDetailComponent . Unlike the HeroDetailComponent , the user changes do not update the crisis entity immediately. Instead, the application updates the entity when the user presses the Save button and discards the changes when the user presses the Cancel button.

Both buttons navigate back to the crisis list after save or cancel.

In this scenario, the user could click the heroes link, cancel, push the browser back button, or navigate away without saving.

This example application asks the user to be explicit with a confirmation dialog box that waits asynchronously for the user's response.

You could wait for the user's answer with synchronous, blocking code, however, the application is more responsive—and can do other work—by waiting for the user's answer asynchronously.

Generate a Dialog service to handle user confirmation.

Add a confirm() method to the DialogService to prompt the user to confirm their intent. The window.confirm is a blocking action that displays a modal dialog and waits for user interaction.

It returns an Observable that resolves when the user eventually decides what to do: either to discard changes and navigate away ( true ) or to preserve the pending changes and stay in the crisis editor ( false ).

Generate a guard that checks for the presence of a canDeactivate() method in a component—any component.

Paste the following code into your guard.

While the guard doesn't have to know which component has a deactivate method, it can detect that the CrisisDetailComponent component has the canDeactivate() method and call it. The guard not knowing the details of any component's deactivation method makes the guard reusable.

Alternatively, you could make a component-specific CanDeactivate guard for the CrisisDetailComponent . The canDeactivate() method provides you with the current instance of the component , the current ActivatedRoute , and RouterStateSnapshot in case you needed to access some external information. This would be useful if you only wanted to use this guard for this component and needed to get the component's properties or confirm whether the router should allow navigation away from it.

Looking back at the CrisisDetailComponent , it implements the confirmation workflow for unsaved changes.

Notice that the canDeactivate() method can return synchronously; it returns true immediately if there is no crisis or there are no pending changes. But it can also return a Promise or an Observable and the router will wait for that to resolve to truthy (navigate) or falsy (stay on the current route).

Add the Guard to the crisis detail route in crisis-center-routing.module.ts using the canDeactivate array property.

Now you have given the user a safeguard against unsaved changes.

Resolve : pre-fetching component data

In the Hero Detail and Crisis Detail , the application waited until the route was activated to fetch the respective hero or crisis.

If you were using a real world API, there might be some delay before the data to display is returned from the server. You don't want to display a blank component while waiting for the data.

To improve this behavior, you can pre-fetch data from the server using a resolver so it's ready the moment the route is activated. This also lets you handle errors before routing to the component. There's no point in navigating to a crisis detail for an id that doesn't have a record. It'd be better to send the user back to the Crisis List that shows only valid crisis centers.

In summary, you want to delay rendering the routed component until all necessary data has been fetched.

Fetch data before navigating

At the moment, the CrisisDetailComponent retrieves the selected crisis. If the crisis is not found, the router navigates back to the crisis list view.

The experience might be better if all of this were handled first, before the route is activated. A CrisisDetailResolver service could retrieve a Crisis or navigate away, if the Crisis did not exist, before activating the route and creating the CrisisDetailComponent .

Generate a CrisisDetailResolver service file within the Crisis Center feature area.

Move the relevant parts of the crisis retrieval logic in CrisisDetailComponent.ngOnInit() into the CrisisDetailResolverService . Import the Crisis model, CrisisService , and the Router so you can navigate elsewhere if you can't fetch the crisis.

Be explicit and implement the Resolve interface with a type of Crisis .

Inject the CrisisService and Router and implement the resolve() method. That method could return a Promise , an Observable , or a synchronous return value.

The CrisisService.getCrisis() method returns an observable in order to prevent the route from loading until the data is fetched. The Router guards require an observable to complete , which means it has emitted all of its values. You use the take operator with an argument of 1 to ensure that the Observable completes after retrieving the first value from the Observable returned by the getCrisis() method.

If it doesn't return a valid Crisis , then return an empty Observable , cancel the previous in-progress navigation to the CrisisDetailComponent , and navigate the user back to the CrisisListComponent . The updated resolver service looks like this:

Import this resolver in the crisis-center-routing.module.ts and add a resolve object to the CrisisDetailComponent route configuration.

The CrisisDetailComponent should no longer fetch the crisis. When you re-configured the route, you changed where the crisis is. Update the CrisisDetailComponent to get the crisis from the ActivatedRoute.data.crisis property instead;

Note the following three important points:

The router's Resolve interface is optional. The CrisisDetailResolverService doesn't inherit from a base class. The router looks for that method and calls it if found.

The router calls the resolver in any case where the user could navigate away so you don't have to code for each use case.

Returning an empty Observable in at least one resolver cancels navigation.

The relevant Crisis Center code for this milestone follows.

Query parameters and fragments

In the route parameters section, you only dealt with parameters specific to the route. However, you can use query parameters to get optional parameters available to all routes.

Fragments refer to certain elements on the page identified with an id attribute.

Update the AuthGuard to provide a session_id query that remains after navigating to another route.

Add an anchor element so you can jump to a certain point on the page.

Add the NavigationExtras object to the router.navigate() method that navigates you to the /login route.

You can also preserve query parameters and fragments across navigations without having to provide them again when navigating. In the LoginComponent , you'll add an object as the second argument in the router.navigate() function and provide the queryParamsHandling and preserveFragment to pass along the current query parameters and fragment to the next route.

The queryParamsHandling feature also provides a merge option, which preserves and combines the current query parameters with any provided query parameters when navigating.

To navigate to the Admin Dashboard route after logging in, update admin-dashboard.component.ts to handle the query parameters and fragment.

Query parameters and fragments are also available through the ActivatedRoute service. Like route parameters, the query parameters and fragments are provided as an Observable . The updated Crisis Admin component feeds the Observable directly into the template using the AsyncPipe .

Now, you can click on the Admin button, which takes you to the Login page with the provided queryParamMap and fragment . After you click the login button, notice that you have been redirected to the Admin Dashboard page with the query parameters and fragment still intact in the address bar.

You can use these persistent bits of information for things that need to be provided across pages like authentication tokens or session ids.

The query params and fragment can also be preserved using a RouterLink with the queryParamsHandling and preserveFragment bindings respectively.

Milestone 6: Asynchronous routing

As you've worked through the milestones, the application has naturally gotten larger. At some point you'll reach a point where the application takes a long time to load.

To remedy this issue, use asynchronous routing, which loads feature modules lazily, on request. Lazy loading has multiple benefits.

  • You can load feature areas only when requested by the user.
  • You can speed up load time for users that only visit certain areas of the application.
  • You can continue expanding lazy loaded feature areas without increasing the size of the initial load bundle.

You're already part of the way there. By organizing the application into modules— AppModule , HeroesModule , AdminModule and CrisisCenterModule —you have natural candidates for lazy loading.

Some modules, like AppModule , must be loaded from the start. But others can and should be lazy loaded. The AdminModule , for example, is needed by a few authorized users, so you should only load it when requested by the right people.

Lazy Loading route configuration

Change the admin path in the admin-routing.module.ts from 'admin' to an empty string, '' , the empty path.

Use empty path routes to group routes together without adding any additional path segments to the URL. Users will still visit /admin and the AdminComponent still serves as the Routing Component containing child routes.

Open the AppRoutingModule and add a new admin route to its appRoutes array.

Give it a loadChildren property instead of a children property. The loadChildren property takes a function that returns a promise using the browser's built-in syntax for lazy loading code using dynamic imports import('...') . The path is the location of the AdminModule (relative to the application root). After the code is requested and loaded, the Promise resolves an object that contains the NgModule , in this case the AdminModule .

Note : When using absolute paths, the NgModule file location must begin with src/app in order to resolve correctly. For custom path mapping with absolute paths , you must configure the baseUrl and paths properties in the project tsconfig.json .

When the router navigates to this route, it uses the loadChildren string to dynamically load the AdminModule . Then it adds the AdminModule routes to its current route configuration. Finally, it loads the requested route to the destination admin component.

The lazy loading and re-configuration happen just once, when the route is first requested; the module and routes are available immediately for subsequent requests.

Angular provides a built-in module loader that supports SystemJS to load modules asynchronously. If you were using another bundling tool, such as Webpack, you would use the Webpack mechanism for asynchronously loading modules.

Take the final step and detach the admin feature set from the main application. The root AppModule must neither load nor reference the AdminModule or its files.

In app.module.ts , remove the AdminModule import statement from the top of the file and remove the AdminModule from the NgModule's imports array.

CanLoad : guarding unauthorized loading of feature modules

You're already protecting the AdminModule with a CanActivate guard that prevents unauthorized users from accessing the admin feature area. It redirects to the login page if the user is not authorized.

But the router is still loading the AdminModule even if the user can't visit any of its components. Ideally, you'd only load the AdminModule if the user is logged in.

Add a CanLoad guard that only loads the AdminModule once the user is logged in and attempts to access the admin feature area.

The existing AuthGuard already has the essential logic in its checkLogin() method to support the CanLoad guard.

Open auth.guard.ts . Import the CanLoad interface from @angular/router . Add it to the AuthGuard class's implements list. Then implement canLoad() as follows:

The router sets the canLoad() method's route parameter to the intended destination URL. The checkLogin() method redirects to that URL once the user has logged in.

Now import the AuthGuard into the AppRoutingModule and add the AuthGuard to the canLoad array property for the admin route. The completed admin route looks like this:

Preloading: background loading of feature areas

In addition to loading modules on-demand, you can load modules asynchronously with preloading.

The AppModule is eagerly loaded when the application starts, meaning that it loads right away. Now the AdminModule loads only when the user clicks on a link, which is called lazy loading.

Preloading lets you load modules in the background so that the data is ready to render when the user activates a particular route. Consider the Crisis Center. It isn't the first view that a user sees. By default, the Heroes are the first view. For the smallest initial payload and fastest launch time, you should eagerly load the AppModule and the HeroesModule .

You could lazy load the Crisis Center. But you're almost certain that the user will visit the Crisis Center within minutes of launching the app. Ideally, the application would launch with just the AppModule and the HeroesModule loaded and then, almost immediately, load the CrisisCenterModule in the background. By the time the user navigates to the Crisis Center, its module is loaded and ready.

How preloading works

After each successful navigation, the router looks in its configuration for an unloaded module that it can preload. Whether it preloads a module, and which modules it preloads, depends upon the preload strategy.

The Router offers two preloading strategies:

  • No preloading, which is the default. Lazy loaded feature areas are still loaded on-demand.
  • Preloading of all lazy loaded feature areas.

The router either never preloads, or preloads every lazy loaded module. The Router also supports custom preloading strategies for fine control over which modules to preload and when.

This section guides you through updating the CrisisCenterModule to load lazily by default and use the PreloadAllModules strategy to load all lazy loaded modules.

Lazy load the crisis center

Update the route configuration to lazy load the CrisisCenterModule . Take the same steps you used to configure AdminModule for lazy loading.

Change the crisis-center path in the CrisisCenterRoutingModule to an empty string.

Add a crisis-center route to the AppRoutingModule .

Set the loadChildren string to load the CrisisCenterModule .

Remove all mention of the CrisisCenterModule from app.module.ts .

Here are the updated modules before enabling preload :

You could try this now and confirm that the CrisisCenterModule loads after you click the "Crisis Center" button.

To enable preloading of all lazy loaded modules, import the PreloadAllModules token from the Angular router package.

The second argument in the RouterModule.forRoot() method takes an object for additional configuration options. The preloadingStrategy is one of those options. Add the PreloadAllModules token to the forRoot() call:

This configures the Router preloader to immediately load all lazy loaded routes (routes with a loadChildren property).

When you visit http://localhost:4200 , the /heroes route loads immediately upon launch and the router starts loading the CrisisCenterModule right after the HeroesModule loads.

Currently, the AdminModule does not preload because CanLoad is blocking it.

CanLoad blocks preload

The PreloadAllModules strategy does not load feature areas protected by a CanLoad guard.

You added a CanLoad guard to the route in the AdminModule a few steps back to block loading of that module until the user is authorized. That CanLoad guard takes precedence over the preload strategy.

If you want to preload a module as well as guard against unauthorized access, remove the canLoad() guard method and rely on the canActivate() guard alone.

Custom Preloading Strategy

Preloading every lazy loaded module works well in many situations. However, in consideration of things such as low bandwidth and user metrics, you can use a custom preloading strategy for specific feature modules.

This section guides you through adding a custom strategy that only preloads routes whose data.preload flag is set to true . Recall that you can add anything to the data property of a route.

Set the data.preload flag in the crisis-center route in the AppRoutingModule .

Generate a new SelectivePreloadingStrategy service.

Replace the contents of selective-preloading-strategy.service.ts with the following:

SelectivePreloadingStrategyService implements the PreloadingStrategy , which has one method, preload() .

The router calls the preload() method with two arguments:

  • The route to consider.
  • A loader function that can load the routed module asynchronously.

An implementation of preload must return an Observable . If the route does preload, it returns the observable returned by calling the loader function. If the route does not preload, it returns an Observable of null .

In this sample, the preload() method loads the route if the route's data.preload flag is truthy.

As a side-effect, SelectivePreloadingStrategyService logs the path of a selected route in its public preloadedModules array.

Shortly, you'll extend the AdminDashboardComponent to inject this service and display its preloadedModules array.

But first, make a few changes to the AppRoutingModule .

  • Import SelectivePreloadingStrategyService into AppRoutingModule .
  • Replace the PreloadAllModules strategy in the call to forRoot() with this SelectivePreloadingStrategyService .

Now edit the AdminDashboardComponent to display the log of preloaded routes.

  • Import the SelectivePreloadingStrategyService .
  • Inject it into the dashboard's constructor.
  • Update the template to display the strategy service's preloadedModules array.

Now the file is as follows:

Once the application loads the initial route, the CrisisCenterModule is preloaded. Verify this by logging in to the Admin feature area and noting that the crisis-center is listed in the Preloaded Modules . It also logs to the browser's console.

Migrating URLs with redirects

You've setup the routes for navigating around your application and used navigation imperatively and declaratively. But like any application, requirements change over time. You've setup links and navigation to /heroes and /hero/:id from the HeroListComponent and HeroDetailComponent components. If there were a requirement that links to heroes become superheroes , you would still want the previous URLs to navigate correctly. You also don't want to update every link in your application, so redirects makes refactoring routes trivial.

Changing /heroes to /superheroes

This section guides you through migrating the Hero routes to new URLs. The Router checks for redirects in your configuration before navigating, so each redirect is triggered when needed. To support this change, add redirects from the old routes to the new routes in the heroes-routing.module .

Notice two different types of redirects. The first change is from /heroes to /superheroes without any parameters. The second change is from /hero/:id to /superhero/:id , which includes the :id route parameter. Router redirects also use powerful pattern-matching, so the Router inspects the URL and replaces route parameters in the path with their appropriate destination. Previously, you navigated to a URL such as /hero/15 with a route parameter id of 15 .

The Router also supports query parameters and the fragment when using redirects. When using absolute redirects, the Router uses the query parameters and the fragment from the redirectTo in the route config. When using relative redirects, the Router use the query params and the fragment from the source URL.

Currently, the empty path route redirects to /heroes , which redirects to /superheroes . This won't work because the Router handles redirects once at each level of routing configuration. This prevents chaining of redirects, which can lead to endless redirect loops.

Instead, update the empty path route in app-routing.module.ts to redirect to /superheroes .

A routerLink isn't tied to route configuration, so update the associated router links to remain active when the new route is active. Update the app.component.ts template for the /heroes routerLink .

Update the goToHeroes() method in the hero-detail.component.ts to navigate back to /superheroes with the optional route parameters.

With the redirects setup, all previous routes now point to their new destinations and both URLs still function as intended.

Inspect the router's configuration

To determine if your routes are actually evaluated in the proper order , you can inspect the router's configuration.

Do this by injecting the router and logging to the console its config property. For example, update the AppModule as follows and look in the browser console window to see the finished route configuration.

Final application

For the completed router application, see the for the final source code.

© 2010–2021 Google, Inc. Licensed under the Creative Commons Attribution License 4.0. https://v12.angular.io/guide/router-tutorial-toh

  • Ahmed Bouchefra
  • Nov 28, 2018

A Complete Guide To Routing In Angular

  • 14 min read
  • Apps , PWA , Native , Angular , Service Workers
  • Share on Twitter ,  LinkedIn

About The Author

Ahmed is a technical author and web developer living in Morocco with a Master’s degree in software development. He authors technical content about … More about Ahmed ↬

Email Newsletter

Weekly tips on front-end & UX . Trusted by 200,000+ folks.

In case you’re still not quite familiar with Angular 7, I’d like to bring you closer to everything this impressive front-end framework has to offer. I’ll walk you through an Angular demo app that shows different concepts related to the Router, such as:

  • The router outlet,
  • Routes and paths,
  • Navigation.

I’ll also show you how to use Angular CLI v7 to generate a demo project where we’ll use the Angular router to implement routing and navigation. But first, allow me to introduce you to Angular and go over some of the important new features in its latest version.

Introducing Angular 7

Angular is one of the most popular front-end frameworks for building client-side web applications for the mobile and desktop web. It follows a component-based architecture where each component is an isolated and re-usable piece of code that controls a part of the app’s UI.

A component in Angular is a TypeScript class decorated with the @Component decorator. It has an attached template and CSS stylesheets that form the component’s view.

Angular 7, the latest version of Angular has been recently released with new features particularly in CLI tooling and performance, such as:

  • CLI Prompts: A common command like ng add and ng new can now prompt the user to choose the functionalities to add into a project like routing and stylesheets format, etc.
  • Adding scrolling to Angular Material CDK (Component DevKit).
  • Adding drag and drop support to Angular Material CDK.
  • Projects are also defaulted to use Budget Bundles which will warn developers when their apps are passing size limits. By default, warnings are thrown when the size has more than 2MB and errors at 5MB. You can also change these limits in your angular.json file. etc.

Introducing Angular Router

Angular Router is a powerful JavaScript router built and maintained by the Angular core team that can be installed from the @angular/router package. It provides a complete routing library with the possibility to have multiple router outlets, different path matching strategies, easy access to route parameters and route guards to protect components from unauthorized access.

The Angular router is a core part of the Angular platform. It enables developers to build Single Page Applications with multiple views and allow navigation between these views.

Let’s now see the essential Router concepts in more details.

The Router-Outlet

The Router-Outlet is a directive that’s available from the router library where the Router inserts the component that gets matched based on the current browser’s URL. You can add multiple outlets in your Angular application which enables you to implement advanced routing scenarios.

Any component that gets matched by the Router will render it as a sibling of the Router outlet.

Routes And Paths

Routes are definitions (objects) comprised from at least a path and a component (or a redirectTo path) attributes. The path refers to the part of the URL that determines a unique view that should be displayed, and component refers to the Angular component that needs to be associated with a path. Based on a route definition that we provide (via a static RouterModule.forRoot(routes) method), the Router is able to navigate the user to a specific view.

Each Route maps a URL path to a component.

The path can be empty which denotes the default path of an application and it’s usually the start of the application.

The path can take a wildcard string ( ** ). The router will select this route if the requested URL doesn’t match any paths for the defined routes. This can be used for displaying a “Not Found” view or redirecting to a specific view if no match is found.

This is an example of a route:

If this route definition is provided to the Router configuration, the router will render ContactListComponent when the browser URL for the web application becomes /contacts .

Route Matching Strategies

The Angular Router provides different route matching strategies. The default strategy is simply checking if the current browser’s URL is prefixed with the path .

For example our previous route:

Could be also written as:

The patchMath attribute specifies the matching strategy. In this case, it’s prefix which is the default.

The second  matching strategy is full . When it’s specified for a route, the router will check if the the path is exactly equal to the path of the current browser’s URL:

Route Params

Creating routes with parameters is a common feature in web apps. Angular Router allows you to access parameters in different ways:

  • Using the ActivatedRoute service,
  • Using the ParamMap observable available starting with v4.

You can create a route parameter using the colon syntax. This is an example route with an id parameter:

Route Guards

A route guard is a feature of the Angular Router that allows developers to run some logic when a route is requested, and based on that logic, it allows or denies the user access to the route. It’s commonly used to check if a user is logged in and has the authorization before he can access a page.

You can add a route guard by implementing the CanActivate interface available from the @angular/router package and extends the canActivate() method which holds the logic to allow or deny access to the route. For example, the following guard will always allow access to a route:

You can then protect a route with the guard using the canActivate attribute:

Navigation Directive

The Angular Router provides the routerLink directive to create navigation links. This directive takes the path associated with the component to navigate to. For example:

Multiple Outlets And Auxiliary Routes

Angular Router supports multiple outlets in the same application.

A component has one associated primary route and can have auxiliary routes. Auxiliary routes enable developers to navigate multiple routes at the same time.

To create an auxiliary route, you’ll need a named router outlet where the component associated with the auxiliary route will be displayed.

  • The outlet with no name is the primary outlet.
  • All outlets should have a name except for the primary outlet.

You can then specify the outlet where you want to render your component using the outlet attribute:

Creating An Angular 7 Demo Project

In this section, we’ll see a practical example of how to set up and work with the Angular Router. You can see the live demo we’ll be creating and the GitHub repository for the project.

Installing Angular CLI v7

Angular CLI requires Node 8.9+ , with NPM 5.5.1+ . You need to make sure you have these requirements installed on your system then run the following command to install the latest version of Angular CLI:

This will install the Angular CLI globally.

Note : You may want to use sudo to install packages globally, depending on your npm configuration.

Creating An Angular 7 Project

Creating a new project is one command away, you simply need to run the following command:

The CLI will ask you if you would like to add routing (type N for No because we’ll see how we can add routing manually) and which stylesheet format would you like to use, choose CSS, the first option then hit Enter . The CLI will create a folder structure with the necessary files and install the project’s required dependencies.

Creating A Fake Back-End Service

Since we don’t have a real back-end to interact with, we’ll create a fake back-end using the angular-in-memory-web-api library which is an in-memory web API for Angular demos and tests that emulates CRUD operations over a REST API.

It works by intercepting the HttpClient requests sent to the remote server and redirects them to a local in-memory data store that we need to create.

To create a fake back-end, we need to follow the next steps:

  • First, we install the angular-in-memory-web-api module,
  • Next, we create a service which returns fake data,
  • Finally, configure the application to use the fake back-end.

In your terminal run the following command to install the angular-in-memory-web-api module from npm:

Next, generate a back-end service using:

Open the src/app/backend.service.ts file and import InMemoryDbService from the angular-in-memory-web-api module:

The service class needs to implement InMemoryDbService and then override the createDb() method:

We simply create an array of contacts and return them. Each contact should have an id.

Finally, we simply need to import InMemoryWebApiModule into the app.module.ts file, and provide our fake back-end service.

Next create a ContactService which encapsulates the code for working with contacts:

Open the src/app/contact.service.ts file and update it to look similar to the following code:

We added two methods:

  • getContacts() For getting all contacts.
  • getContact() For getting a contact by id.

You can set the API_URL to whatever URL since we are not going to use a real back-end. All requests will be intercepted and sent to the in-memory back-end.

Creating Our Angular Components

Before we can see how to use the different Router features, let’s first create a bunch of components in our project.

Head over to your terminal and run the following commands:

This will generate two ContactListComponent and ContactDetailComponent components and add them to the main app module.

Setting Up Routing

In most cases, you’ll use the Angular CLI to create projects with routing setup but in this case, we’ll add it manually so we can get a better idea how routing works in Angular.

Adding The Routing Module

We need to add AppRoutingModule which will contain our application routes and a router outlet where Angular will insert the currently matched component depending on the browser current URL.

We’ll see:

  • How to create an Angular Module for routing and import it;
  • How to add routes to different components;
  • How to add the router outlet.

First, let’s start by creating a routing module in an app-routing.module.ts file. Inside the src/app create the file using:

Open the file and add the following code:

We start by importing the NgModule from the @angular/core package which is a TypeScript decorator used to create an Angular module.

We also import the RouterModule and Routes classes from the @angular/router package . RouterModule provides static methods like RouterModule.forRoot() for passing a configuration object to the Router.

Next, we define a constant routes array of type Routes which will be used to hold information for each route.

Finally, we create and export a module called AppRoutingModule (You can call it whatever you want) which is simply a TypeScript class decorated with the @NgModule decorator that takes some meta information object. In the imports attribute of this object, we call the static RouterModule.forRoot(routes) method with the routes array as a parameter. In the exports array we add the RouterModule .

Importing The Routing Module

Next, we need to import this module routing into the main app module that lives in the src/app/app.module.ts file:

We import the AppRoutingModule from ./app-routing.module and we add it in the imports array of the main module.

Adding The Router Outlet

Finally, we need to add the router outlet. Open the src/app/app.component.html file which contains the main app template and add the <router-outlet> component:

This is where the Angular Router will render the component that corresponds to current browser’s path.

That’s all steps we need to follow in order to manually setup routing inside an Angular project.

Creating Routes

Now, let’s add routes to our two components. Open the src/app/app-routing.module.ts file and add the following routes to the routes array:

Make sure to import the two components in the routing module:

Now we can access the two components from the /contacts and contact/:id paths.

Adding Navigation Links

Next let’s add navigation links to our app template using the routerLink directive. Open the src/app/app.component.html and add the following code on top of the router outlet:

Next we need to display the list of contacts in ContactListComponent . Open the src/app/contact-list.component.ts then add the following code:

We create a contacts array to hold the contacts. Next, we inject ContactService and we call the getContacts() method of the instance (on the ngOnInit life-cycle event) to get contacts and assign them to the contacts array.

Next open the src/app/contact-list/contact-list.component.html file and add:

We loop through the contacts and display each contact’s name and email. We also create a link to each contact’s details component using the routerLink directive.

This is a screen shot of the component:

When we click on the Go to details link, it will take us to ContactDetailsComponent . The route has an id parameter, let’s see how we can access it from our component.

Open the src/app/contact-detail/contact-detail.component.ts file and change the code to look similar to the following code:

We inject ContactService and ActivatedRoute into the component. In ngOnInit() life-cycle event we retrieve the id parameter that will be passed from the route and use it to get the contact’s details that we assign to a contact object.

Open the src/app/contact-detail/contact-detail.component.html file and add:

When we first visit our application from 127.0.0.1:4200/ , the outlet doesn’t render any component so let’s redirect the empty path to the contacts path by adding the following route to the routes array:

We want to match the exact empty path, that’s why we specify the full match strategy.

In this tutorial, we’ve seen how to use the Angular Router to add routing and navigation into our application. We’ve seen different concepts like the Router outlet, routes, and paths and we created a demo to practically show the different concepts. You can access the code from this repository .

Further Reading

  • The Growing Need For Effective Password Management
  • Which Is Best: A Loyalty Program PWA Or Mobile App?
  • Animating React Components With GreenSock
  • Things I Wish I Had Known About Angular When I Started

Smashing Newsletter

Tips on front-end & UX, delivered weekly in your inbox. Just the things you can actually use.

Front-End & UX Workshops, Online

With practical takeaways, live sessions, video recordings and a friendly Q&A.

TypeScript in 50 Lessons

Everything TypeScript, with code walkthroughs and examples. And other printed books.

Routing & Navigation

Discover the basics of screen navigation with the Angular Router.

The Angular Router enables navigation from one view to the next as users perform application tasks.

The browser is a familiar model of application navigation:

  • Enter a URL in the address bar and the browser navigates to a corresponding page.
  • Click links on the page and the browser navigates to a new page.
  • Click the browser's back and forward buttons and the browser navigates backward and forward through the history of pages you've seen.

The Angular Router ("the router") borrows from this model. It can interpret a browser URL as an instruction to navigate to a client-generated view. It can pass optional parameters along to the supporting view component that help it decide what specific content to present. You can bind the router to links on a page and it will navigate to the appropriate application view when the user clicks a link. You can navigate imperatively when the user clicks a button, selects from a drop box, or in response to some other stimulus from any source. And the router logs activity in the browser's history journal so the back and forward buttons work as well.

<base href>

Router imports, configuration, router outlet, router links, router state, the sample application.

  • Setting the base href

Importing from the router library

Define routes, the appcomponent shell, routeroutlet, routerlink binding, routerlinkactive binding, wildcard route, the default route to heroes, refactor the routing configuration into a routing module.

  • Do you need a Routing Module?

Add heroes functionality

Hero feature routing requirements, hero feature route configuration, add the routing module to the heroesmodule, remove duplicate hero routes, import hero module into appmodule, module import order matters.

  • Route Definition with a parameter

Navigate to hero detail imperatively

Setting the route parameters in the list view, activatedroute: the one-stop-shop for route information, observable params and component reuse.

  • Snapshot: the no-observable alternative

Navigating back to the list component

Route parameters: required or optional, heroes list: optionally selecting a hero, route parameters in the activatedroute service, adding animations to the routed component, milestone 3 wrap up, a crisis center with child routes, child routing component, child route configuration, import crisis center module into the appmodule routes, relative navigation, navigate to crisis detail with a relative url, displaying multiple routes in named outlets, secondary routes, add a secondary route, secondary route navigation: merging routes during navigation, clearing secondary routes, canactivate : requiring authentication, component-less route: grouping routes without a component, guard the admin feature, teach authguard to authenticate.

  • Add the login component

CanActivateChild : guarding child routes

Candeactivate : handling unsaved changes, cancel and save, resolve : pre-fetching component data, fetch data before navigating, query parameters and fragments.

  • Lazy loading route configuration

CanLoad Guard: guarding unauthorized loading of feature modules

Preloading: background loading of feature areas, how preloading works, lazy load the crisis center, canload blocks preload, custom preloading strategy, inspect the router's configuration, wrap up and final app, appendix: link parameters array, appendix: locationstrategy and browser url styles.

This guide proceeds in phases, marked by milestones, starting from a simple two-pager and building toward a modular, multi-view design with child routes.

An introduction to a few core router concepts will help orient you to the details that follow.

Most routing applications should add a <base> element to the index.html as the first child in the <head> tag to tell the router how to compose navigation URLs.

If the app folder is the application root, as it is for the sample application, set the href value exactly as shown here.

src/index.html (base-href)

The Angular Router is an optional service that presents a particular component view for a given URL. It is not part of the Angular core. It is in its own library package, @angular/router . Import what you need from it as you would from any other Angular package.

src/app/app.module.ts (import)

You'll learn about more options in the details below .

A routed Angular application has one singleton instance of the Router service. When the browser's URL changes, that router looks for a corresponding Route from which it can determine the component to display.

A router has no routes until you configure it. The following example creates four route definitions, configures the router via the RouterModule.forRoot method, and adds the result to the AppModule 's imports array.

src/app/app.module.ts (excerpt)

The appRoutes array of routes describes how to navigate. Pass it to the RouterModule.forRoot method in the module imports to configure the router.

Each Route maps a URL path to a component. There are no leading slashes in the path . The router parses and builds the final URL for you, allowing you to use both relative and absolute paths when navigating between application views.

The :id in the first route is a token for a route parameter. In a URL such as /hero/42 , "42" is the value of the id parameter. The corresponding HeroDetailComponent will use that value to find and present the hero whose id is 42. You'll learn more about route parameters later in this guide.

The data property in the third route is a place to store arbitrary data associated with this specific route. The data property is accessible within each activated route. Use it to store items such as page titles, breadcrumb text, and other read-only, static data. You'll use the resolve guard to retrieve dynamic data later in the guide.

The empty path in the fourth route represents the default path for the application, the place to go when the path in the URL is empty, as it typically is at the start. This default route redirects to the route for the /heroes URL and, therefore, will display the HeroesListComponent .

The ** path in the last route is a wildcard . The router will select this route if the requested URL doesn't match any paths for routes defined earlier in the configuration. This is useful for displaying a "404 - Not Found" page or redirecting to another route.

The order of the routes in the configuration matters and this is by design. The router uses a first-match wins strategy when matching routes, so more specific routes should be placed above less specific routes. In the configuration above, routes with a static path are listed first, followed by an empty path route, that matches the default route. The wildcard route comes last because it matches every URL and should be selected only if no other routes are matched first.

Given this configuration, when the browser URL for this application becomes /heroes , the router matches that URL to the route path /heroes and displays the HeroListComponent after a RouterOutlet that you've placed in the host view's HTML.

Now you have routes configured and a place to render them, but how do you navigate? The URL could arrive directly from the browser address bar. But most of the time you navigate as a result of some user action such as the click of an anchor tag.

Consider the following template:

The RouterLink directives on the anchor tags give the router control over those elements. The navigation paths are fixed, so you can assign a string to the routerLink (a "one-time" binding).

Had the navigation path been more dynamic, you could have bound to a template expression that returned an array of route link parameters (the link parameters array ). The router resolves that array into a complete URL.

The RouterLinkActive directive on each anchor tag helps visually distinguish the anchor for the currently selected "active" route. The router adds the active CSS class to the element when the associated RouterLink becomes active. You can add this directive to the anchor or to its parent element.

After the end of each successful navigation lifecycle, the router builds a tree of ActivatedRoute objects that make up the current state of the router. You can access the current RouterState from anywhere in the application using the Router service and the routerState property.

Each ActivatedRoute in the RouterState provides methods to traverse up and down the route tree to get information from parent, child and sibling routes.

The application has a configured router. The shell component has a RouterOutlet where it can display views produced by the router. It has RouterLink s that users can click to navigate via the router.

Here are the key Router terms and their meanings:

This guide describes development of a multi-page routed sample application. Along the way, it highlights design decisions and describes key features of the router such as:

  • Organizing the application features into modules.
  • Navigating to a component ( Heroes link to "Heroes List").
  • Including a route parameter (passing the Hero id while routing to the "Hero Detail").
  • Child routes (the Crisis Center has its own routes).
  • The CanActivate guard (checking route access).
  • The CanActivateChild guard (checking child route access).
  • The CanDeactivate guard (ask permission to discard unsaved changes).
  • The Resolve guard (pre-fetching route data).
  • Lazy loading feature modules.
  • The CanLoad guard (check before loading feature module assets).

The guide proceeds as a sequence of milestones as if you were building the app step-by-step. But, it is not a tutorial and it glosses over details of Angular application construction that are more thoroughly covered elsewhere in the documentation.

The sample application in action

Imagine an application that helps the Hero Employment Agency run its business. Heroes need work and the agency finds crises for them to solve.

The application has three main feature areas:

  • A Crisis Center for maintaining the list of crises for assignment to heroes.
  • A Heroes area for maintaining the list of heroes employed by the agency.
  • An Admin area to manage the list of crises and heroes.

Once the app warms up, you'll see a row of navigation buttons and the Heroes view with its list of heroes.

Hero List

Select one hero and the app takes you to a hero editing screen.

Crisis Center Detail

Alter the name. Click the "Back" button and the app returns to the heroes list which displays the changed hero name. Notice that the name change took effect immediately.

Had you clicked the browser's back button instead of the "Back" button, the app would have returned you to the heroes list as well. Angular app navigation updates the browser history as normal web navigation does.

Now click the Crisis Center link for a list of ongoing crises.

Crisis Center List

Select a crisis and the application takes you to a crisis editing screen. The Crisis Detail appears in a child view on the same page, beneath the list.

Alter the name of a crisis. Notice that the corresponding name in the crisis list does not change.

Crisis Center Detail

Unlike Hero Detail , which updates as you type, Crisis Detail changes are temporary until you either save or discard them by pressing the "Save" or "Cancel" buttons. Both buttons navigate back to the Crisis Center and its list of crises.

Do not click either button yet . Click the browser back button or the "Heroes" link instead.

Up pops a dialog box.

Confirm Dialog

You can say "OK" and lose your changes or click "Cancel" and continue editing.

Behind this behavior is the router's CanDeactivate guard. The guard gives you a chance to clean-up or ask the user's permission before navigating away from the current view.

The Admin and Login buttons illustrate other router capabilities to be covered later in the guide. This short introduction will do for now.

Proceed to the first application milestone.

Milestone 1: Getting started with the router

Begin with a simple version of the app that navigates between two empty views.

Set the <base href>

The router uses the browser's history.pushState for navigation. Thanks to pushState , you can make in-app URL paths look the way you want them to look, e.g. localhost:3000/crisis-center . The in-app URLs can be indistinguishable from server URLs.

Modern HTML5 browsers were the first to support pushState which is why many people refer to these URLs as "HTML5 style" URLs.

HTML5 style navigation is the router default. In the LocationStrategy and browser URL styles Appendix, learn why HTML5 style is preferred, how to adjust its behavior, and how to switch to the older hash (#) style, if necessary.

You must add a <base href> element to the app's index.html for pushState routing to work. The browser uses the <base href> value to prefix relative URLs when referencing CSS files, scripts, and images.

Add the <base> element just after the <head> tag. If the app folder is the application root, as it is for this application, set the href value in index.html exactly as shown here.

A live coding environment like Plunker sets the application base address dynamically so you can't specify a fixed address. That's why the example code replaces the <base href...> with a script that writes the <base> tag on the fly.

You only need this trick for the live example, not production code.

Begin by importing some symbols from the router library. The Router is in its own @angular/router package. It's not part of the Angular core. The router is an optional service because not all applications need routing and, depending on your requirements, you may need a different routing library.

You teach the router how to navigate by configuring it with routes.

A router must be configured with a list of route definitions.

The first configuration defines an array of two routes with simple paths leading to the CrisisListComponent and HeroListComponent .

Each definition translates to a Route object which has two things: a path , the URL path segment for this route; and a component , the component associated with this route.

The router draws upon its registry of definitions when the browser URL changes or when application code tells the router to navigate along a route path.

In simpler terms, you might say this of the first route:

When the browser's location URL changes to match the path segment /crisis-center , then the router activates an instance of the CrisisListComponent and displays its view.

When the application requests navigation to the path /crisis-center , the router activates an instance of CrisisListComponent , displays its view, and updates the browser's address location and history with the URL for that path.

Here is the first configuration. Pass the array of routes, appRoutes , to the RouterModule.forRoot method. It returns a module, containing the configured Router service provider, plus other providers that the routing library requires. Once the application is bootstrapped, the Router performs the initial navigation based on the current browser URL.

src/app/app.module.ts (first-config)

Adding the configured RouterModule to the AppModule is sufficient for simple route configurations. As the application grows, you'll want to refactor the routing configuration into a separate file and create a Routing Module , a special type of Service Module dedicated to the purpose of routing in feature modules.

Providing the RouterModule in the AppModule makes the Router available everywhere in the application.

The root AppComponent is the application shell. It has a title, a navigation bar with two links, and a router outlet where the router swaps views on and off the page. Here's what you get:

Shell

The corresponding component template looks like this:

The RouterOutlet is a directive from the router library that marks the spot in the template where the router should display the views for that outlet.

The router adds the <router-outlet> element to the DOM and subsequently inserts the navigated view element immediately after the <router-outlet> .

Above the outlet, within the anchor tags, you see attribute bindings to the RouterLink directive that look like routerLink="..." .

The links in this example each have a string path, the path of a route that you configured earlier. There are no route parameters yet.

You can also add more contextual information to the RouterLink by providing query string parameters or a URL fragment for jumping to different areas on the page. Query string parameters are provided through the [queryParams] binding which takes an object (e.g. { name: 'value' } ), while the URL fragment takes a single value bound to the [fragment] input binding.

Learn about the how you can also use the link parameters array in the appendix below .

On each anchor tag, you also see property bindings to the RouterLinkActive directive that look like routerLinkActive="..." .

The template expression to the right of the equals (=) contains a space-delimited string of CSS classes that the Router will add when this link is active (and remove when the link is inactive). You can also set the RouterLinkActive directive to a string of classes such as [routerLinkActive]="active fluffy" or bind it to a component property that returns such a string.

The RouterLinkActive directive toggles css classes for active RouterLink s based on the current RouterState . This cascades down through each level of the route tree, so parent and child router links can be active at the same time. To override this behavior, you can bind to the [routerLinkActiveOptions] input binding with the { exact: true } expression. By using { exact: true } , a given RouterLink will only be active if its URL is an exact match to the current URL.

Router directives

RouterLink , RouterLinkActive and RouterOutlet are directives provided by the Angular RouterModule package. They are readily available for you to use in the template.

The current state of app.component.ts looks like this:

src/app/app.component.ts (excerpt)

You've created two routes in the app so far, one to /crisis-center and the other to /heroes . Any other URL causes the router to throw an error and crash the app.

Add a wildcard route to intercept invalid URLs and handle them gracefully. A wildcard route has a path consisting of two asterisks. It matches every URL. The router will select this route if it can't match a route earlier in the configuration. A wildcard route can navigate to a custom "404 Not Found" component or redirect to an existing route.

The router selects the route with a first match wins strategy. Wildcard routes are the least specific routes in the route configuration. Be sure it is the last route in the configuration.

To test this feature, add a button with a RouterLink to the HeroListComponent template and set the link to "/sidekicks" .

src/app/hero-list.component.ts (excerpt)

The application will fail if the user clicks that button because you haven't defined a "/sidekicks" route yet.

Instead of adding the "/sidekicks" route, define a wildcard route instead and have it navigate to a simple PageNotFoundComponent .

src/app/app.module.ts (wildcard)

Create the PageNotFoundComponent to display when users visit invalid URLs.

src/app/not-found.component.ts (404 component)

As with the other components, add the PageNotFoundComponent to the AppModule declarations.

Now when the user visits /sidekicks , or any other invalid URL, the browser displays "Page not found". The browser address bar continues to point to the invalid URL.

When the application launches, the initial URL in the browser bar is something like:

That doesn't match any of the configured routes which means that the application won't display any component when it's launched. The user must click one of the links to trigger a navigation and display a component.

It would be nicer if the application had a default route that displayed the list of heroes immediately, just as it will when the user clicks the "Heroes" link or pastes localhost:3000/heroes into the address bar.

Redirecting routes

The preferred solution is to add a redirect route that translates the initial relative URL ( '' ) to the desired default path ( /heroes ). The browser address bar shows .../heroes as if you'd navigated there directly.

Add the default route somewhere above the wildcard route. It's just above the wildcard route in the following excerpt showing the complete appRoutes for this milestone.

src/app/app-routing.module.ts (appRoutes)

A redirect route requires a pathMatch property to tell the router how to match a URL to the path of a route. The router throws an error if you don't. In this app, the router should select the route to the HeroListComponent only when the entire URL matches '' , so set the pathMatch value to 'full' .

Technically, pathMatch = 'full' results in a route hit when the remaining , unmatched segments of the URL match '' . In this example, the redirect is in a top level route so the remaining URL and the entire URL are the same thing.

The other possible pathMatch value is 'prefix' which tells the router to match the redirect route when the remaining URL begins with the redirect route's prefix path.

Don't do that here. If the pathMatch value were 'prefix' , every URL would match '' .

Try setting it to 'prefix' then click the Go to sidekicks button. Remember that's a bad URL and you should see the "Page not found" page. Instead, you're still on the "Heroes" page. Enter a bad URL in the browser address bar. You're instantly re-routed to /heroes . Every URL, good or bad, that falls through to this route definition will be a match.

The default route should redirect to the HeroListComponent only when the entire url is '' . Remember to restore the redirect to pathMatch = 'full' .

Learn more in Victor Savkin's post on redirects .

Basics wrap up

You've got a very basic navigating app, one that can switch between two views when the user clicks a link.

You've learned how to do the following:

  • Load the router library.
  • Add a nav bar to the shell template with anchor tags, routerLink and routerLinkActive directives.
  • Add a router-outlet to the shell template where views will be displayed.
  • Configure the router module with RouterModule.forRoot .
  • Set the router to compose HTML5 browser URLs.
  • handle invalid routes with a wildcard route.
  • navigate to the default route when the app launches with an empty path.

The rest of the starter app is mundane, with little interest from a router perspective. Here are the details for readers inclined to build the sample through to this milestone.

The starter app's structure looks like this:

Here are the files discussed in this milestone.

Milestone 2: Routing module

In the initial route configuration, you provided a simple setup with two routes used to configure the application for routing. This is perfectly fine for simple routing. As the application grows and you make use of more Router features, such as guards, resolvers, and child routing, you'll naturally want to refactor the routing configuration into its own file. We recommend moving the routing information into a special-purpose module called a Routing Module .

The Routing Module has several characteristics:

  • Separates routing concerns from other application concerns.
  • Provides a module to replace or remove when testing the application.
  • Provides a well-known location for routing service providers including guards and resolvers.
  • Does not declare components .

Create a file named app-routing.module.ts in the /app folder to contain the routing module.

Import the CrisisListComponent and the HeroListComponent components just like you did in the app.module.ts . Then move the Router imports and routing configuration, including RouterModule.forRoot , into this routing module.

Following convention, add a class name AppRoutingModule and export it so you can import it later in AppModule .

Finally, re-export the Angular RouterModule by adding it to the module exports array. By re-exporting the RouterModule here and importing AppRoutingModule in AppModule , the components declared in AppModule will have access to router directives such as RouterLink and RouterOutlet .

After these steps, the file should look like this.

src/app/app-routing.module.ts

Next, update the app.module.ts file, first importing the newly created AppRoutingModule from app-routing.module.ts , then replacing RouterModule.forRoot in the imports array with the AppRoutingModule .

src/app/app.module.ts

Later in this guide you will create multiple routing modules and discover that you must import those routing modules in the correct order .

The application continues to work just the same, and you can use AppRoutingModule as the central place to maintain future routing configuration.

Do you need a Routing Module ?

The Routing Module replaces the routing configuration in the root or feature module. Either configure routes in the Routing Module or within the module itself but not in both.

The Routing Module is a design choice whose value is most obvious when the configuration is complex and includes specialized guard and resolver services. It can seem like overkill when the actual configuration is dead simple.

Some developers skip the Routing Module (for example, AppRoutingModule ) when the configuration is simple and merge the routing configuration directly into the companion module (for example, AppModule ).

Choose one pattern or the other and follow that pattern consistently.

Most developers should always implement a Routing Module for the sake of consistency. It keeps the code clean when configuration becomes complex. It makes testing the feature module easier. Its existence calls attention to the fact that a module is routed. It is where developers expect to find and expand routing configuration.

Milestone 3: Heroes feature

You've seen how to navigate using the RouterLink directive. Now you'll learn the following:

  • Organize the app and routes into feature areas using modules.
  • Navigate imperatively from one component to another.
  • Pass required and optional information in route parameters.

Here's how the user will experience this version of the app:

A typical application has multiple feature areas , each dedicated to a particular business purpose.

While you could continue to add files to the src/app/ folder, that is unrealistic and ultimately not maintainable. Most developers prefer to put each feature area in its own folder.

You are about to break up the app into different feature modules , each with its own concerns. Then you'll import into the main module and navigate among them.

Follow these steps:

  • Create the src/app/heroes folder; you'll be adding files implementing hero management there.
  • Delete the placeholder hero-list.component.ts that's in the app folder.
  • Create a new hero-list.component.ts under src/app/heroes .
  • "Services" tutorial .
  • Delete the selector (routed components don't need them).
  • Delete the <h1> .
  • Relabel the <h2> to <h2>HEROES</h2> .
  • Delete the <hero-detail> at the bottom of the template.
  • Rename the AppComponent class to HeroListComponent .
  • Copy the hero-detail.component.ts and the hero.service.ts files into the heroes subfolder.
  • Create a (pre-routing) heroes.module.ts in the heroes folder that looks like this:

src/app/heroes/heroes.module.ts (pre-routing)

When you're done, you'll have these hero management files:

The heroes feature has two interacting components, the hero list and the hero detail. The list view is self-sufficient; you navigate to it, it gets a list of heroes and displays them.

The detail view is different. It displays a particular hero. It can't know which hero to show on its own. That information must come from outside.

When the user selects a hero from the list, the app should navigate to the detail view and show that hero. You tell the detail view which hero to display by including the selected hero's id in the route URL.

Create a new heroes-routing.module.ts in the heroes folder using the same techniques you learned while creating the AppRoutingModule .

src/app/heroes/heroes-routing.module.ts

Put the routing module file in the same folder as its companion module file. Here both heroes-routing.module.ts and heroes.module.ts are in the same src/app/heroes folder.

Consider giving each feature module its own route configuration file. It may seem like overkill early when the feature routes are simple. But routes have a tendency to grow more complex and consistency in patterns pays off over time.

Import the hero components from their new locations in the src/app/heroes/ folder, define the two hero routes, and export the HeroRoutingModule class.

Now that you have routes for the Heroes module, register them with the Router via the RouterModule almost as you did in the AppRoutingModule .

There is a small but critical difference. In the AppRoutingModule , you used the static RouterModule.forRoot method to register the routes and application level service providers. In a feature module you use the static forChild method.

Only call RouterModule.forRoot in the root AppRoutingModule (or the AppModule if that's where you register top level application routes). In any other module, you must call the RouterModule.forChild method to register additional routes.

Add the HeroRoutingModule to the HeroModule just as you added AppRoutingModule to the AppModule .

Open heroes.module.ts . Import the HeroRoutingModule token from heroes-routing.module.ts and add it to the imports array of the HeroesModule . The finished HeroesModule looks like this:

src/app/heroes/heroes.module.ts

The hero routes are currently defined in two places: in the HeroesRoutingModule , by way of the HeroesModule , and in the AppRoutingModule .

Routes provided by feature modules are combined together into their imported module's routes by the router. This allows you to continue defining the feature module routes without modifying the main route configuration.

But you don't want to define the same routes twice. Remove the HeroListComponent import and the /heroes route from the app-routing.module.ts .

Leave the default and the wildcard routes! These are concerns at the top level of the application itself.

src/app/app-routing.module.ts (v2)

The heroes feature module is ready, but the application doesn't know about the HeroesModule yet. Open app.module.ts and revise it as follows.

Import the HeroesModule and add it to the imports array in the @NgModule metadata of the AppModule .

Remove the HeroListComponent from the AppModule 's declarations because it's now provided by the HeroesModule . This is important. There can be only one owner for a declared component. In this case, the Heroes module is the owner of the Heroes components and is making them available to components in the AppModule via the HeroesModule .

As a result, the AppModule no longer has specific knowledge of the hero feature, its components, or its route details. You can evolve the hero feature with more components and different routes. That's a key benefit of creating a separate module for each feature area.

After these steps, the AppModule should look like this:

Look at the module imports array. Notice that the AppRoutingModule is last . Most importantly, it comes after the HeroesModule .

src/app/app.module.ts (module-imports)

The order of route configuration matters. The router accepts the first route that matches a navigation request path.

When all routes were in one AppRoutingModule , you put the default and wildcard routes last, after the /heroes route, so that the router had a chance to match a URL to the /heroes route before hitting the wildcard route and navigating to "Page not found".

The routes are no longer in one file. They are distributed across two modules, AppRoutingModule and HeroesRoutingModule .

Each routing module augments the route configuration in the order of import . If you list AppRoutingModule first, the wildcard route will be registered before the hero routes. The wildcard route — which matches every URL — will intercept the attempt to navigate to a hero route.

Reverse the routing modules and see for yourself that a click of the heroes link results in "Page not found". Learn about inspecting the runtime router configuration below .

Route definition with a parameter

Return to the HeroesRoutingModule and look at the route definitions again. The route to HeroDetailComponent has a twist.

src/app/heroes/heroes-routing.module.ts (excerpt)

Notice the :id token in the path. That creates a slot in the path for a Route Parameter . In this case, the router will insert the id of a hero into that slot.

If you tell the router to navigate to the detail component and display "Magneta", you expect a hero id to appear in the browser URL like this:

If a user enters that URL into the browser address bar, the router should recognize the pattern and go to the same "Magneta" detail view.

Embedding the route parameter token, :id , in the route definition path is a good choice for this scenario because the id is required by the HeroDetailComponent and because the value 15 in the path clearly distinguishes the route to "Magneta" from a route for some other hero.

Users will not navigate to the detail component by clicking a link so you won't add a new RouterLink anchor tag to the shell.

Instead, when the user clicks a hero in the list, you'll ask the router to navigate to the hero detail view for the selected hero.

Start in the HeroListComponent . Revise its constructor so that it acquires the Router and the HeroService by dependency injection:

src/app/heroes/hero-list.component.ts (constructor)

Make the following few changes to the component's template:

The template defines an *ngFor repeater such as you've seen before . There's a (click) event binding to the component's onSelect method which you implement as follows:

src/app/heroes/hero-list.component.ts (select)

The component's onSelect calls the router's navigate method with a link parameters array . You can use this same syntax in a RouterLink if you decide later to navigate in HTML template rather than in component code.

After navigating to the HeroDetailComponent , you expect to see the details of the selected hero. You need two pieces of information: the routing path to the component and the hero's id .

Accordingly, the link parameters array has two items: the routing path and a route parameter that specifies the id of the selected hero.

src/app/heroes/hero-list.component.ts (link-parameters-array)

The router composes the destination URL from the array like this: localhost:3000/hero/15 .

How does the target HeroDetailComponent learn about that id ? Don't analyze the URL. Let the router do it.

The router extracts the route parameter ( id:15 ) from the URL and supplies it to the HeroDetailComponent via the ActivatedRoute service.

The route path and parameters are available through an injected router service called the ActivatedRoute . It has a great deal of useful information including:

url : An Observable of the route path(s), represented as an array of strings for each part of the route path.

data : An Observable that contains the data object provided for the route. Also contains any resolved values from the resolve guard .

params : An Observable that contains the required and optional parameters specific to the route.

queryParams : An Observable that contains the query parameters available to all routes.

fragment : An Observable of the URL fragment available to all routes.

outlet : The name of the RouterOutlet used to render the route. For an unnamed outlet, the outlet name is primary .

routeConfig : The route configuration used for the route that contains the origin path.

parent : an ActivatedRoute that contains the information from the parent route when using child routes .

firstChild : contains the first ActivatedRoute in the list of child routes.

children : contains all the child routes activated under the current route.

Import the Router , ActivatedRoute , and Params tokens from the router package.

src/app/heroes/hero-detail.component.ts (activated route)

Import the switchMap operator because you need it later to process the Observable route parameters.

src/app/heroes/hero-detail.component.ts (switchMap operator import)

As usual, you write a constructor that asks Angular to inject services that the component requires and reference them as private variables.

src/app/heroes/hero-detail.component.ts (constructor)

Later, in the ngOnInit method, you use the ActivatedRoute service to retrieve the parameters for the route, pull the hero id from the parameters and retrieve the hero to display.

Put this data access logic in the ngOnInit method rather than inside the constructor to improve the component's testability. Angular calls the ngOnInit method shortly after creating an instance of the HeroDetailComponent so the hero will be retrieved in time to use it.

Learn more about the ngOnInit method and other component lifecycle hooks in the Lifecycle Hooks guide.

src/app/heroes/hero-detail.component.ts (ngOnInit)

Since the parameters are provided as an Observable , you use the switchMap operator to provide them for the id parameter by name and tell the HeroService to fetch the hero with that id .

The switchMap operator allows you to perform an action with the current value of the Observable , and map it to a new Observable . As with many rxjs operators, switchMap handles an Observable as well as a Promise to retrieve the value they emit.

The switchMap operator will also cancel any in-flight requests if the user re-navigates to the route while still retrieving a hero.

Use the subscribe method to detect id changes and to (re)set the retrieved Hero .

In this example, you retrieve the route params from an Observable . That implies that the route params can change during the lifetime of this component.

They might. By default, the router re-uses a component instance when it re-navigates to the same component type without visiting a different component first. The route parameters could change each time.

Suppose a parent component navigation bar had "forward" and "back" buttons that scrolled through the list of heroes. Each click navigated imperatively to the HeroDetailComponent with the next or previous id .

You don't want the router to remove the current HeroDetailComponent instance from the DOM only to re-create it for the next id . That could be visibly jarring. Better to simply re-use the same component instance and update the parameter.

Unfortunately, ngOnInit is only called once per component instantiation. You need a way to detect when the route parameters change from within the same instance . The observable params property handles that beautifully.

When subscribing to an observable in a component, you almost always arrange to unsubscribe when the component is destroyed.

There are a few exceptional observables where this is not necessary. The ActivatedRoute observables are among the exceptions.

The ActivatedRoute and its observables are insulated from the Router itself. The Router destroys a routed component when it is no longer needed and the injected ActivatedRoute dies with it.

Feel free to unsubscribe anyway. It is harmless and never a bad practice.

Snapshot : the no-observable alternative

This application won't re-use the HeroDetailComponent . The user always returns to the hero list to select another hero to view. There's no way to navigate from one hero detail to another hero detail without visiting the list component in between. Therefore, the router creates a new HeroDetailComponent instance every time.

When you know for certain that a HeroDetailComponent instance will never, never, ever be re-used, you can simplify the code with the snapshot .

The route.snapshot provides the initial value of the route parameters. You can access the parameters directly without subscribing or adding observable operators. It's much simpler to write and read:

src/app/heroes/hero-detail.component.ts (ngOnInit snapshot)

Remember: you only get the initial value of the parameters with this technique. Stick with the observable params approach if there's even a chance that the router could re-use the component. This sample stays with the observable params strategy just in case.

The HeroDetailComponent has a "Back" button wired to its gotoHeroes method that navigates imperatively back to the HeroListComponent .

The router navigate method takes the same one-item link parameters array that you can bind to a [routerLink] directive. It holds the path to the HeroListComponent :

src/app/heroes/hero-detail.component.ts (excerpt)

Use route parameters to specify a required parameter value within the route URL as you do when navigating to the HeroDetailComponent in order to view the hero with id 15:

You can also add optional information to a route request. For example, when returning to the heroes list from the hero detail view, it would be nice if the viewed hero was preselected in the list.

Selected hero

You'll implement this feature in a moment by including the viewed hero's id in the URL as an optional parameter when returning from the HeroDetailComponent .

Optional information takes other forms. Search criteria are often loosely structured, e.g., name='wind*' . Multiple values are common— after='12/31/2015' & before='1/1/2017' —in no particular order— before='1/1/2017' & after='12/31/2015' — in a variety of formats— during='currentYear' .

These kinds of parameters don't fit easily in a URL path . Even if you could define a suitable URL token scheme, doing so greatly complicates the pattern matching required to translate an incoming URL to a named route.

Optional parameters are the ideal vehicle for conveying arbitrarily complex information during navigation. Optional parameters aren't involved in pattern matching and afford flexibility of expression.

The router supports navigation with optional parameters as well as required route parameters. Define optional parameters in a separate object after you define the required route parameters.

In general, prefer a required route parameter when the value is mandatory (for example, if necessary to distinguish one route path from another); prefer an optional parameter when the value is optional, complex, and/or multivariate.

When navigating to the HeroDetailComponent you specified the required id of the hero-to-edit in the route parameter and made it the second item of the link parameters array .

The router embedded the id value in the navigation URL because you had defined it as a route parameter with an :id placeholder token in the route path :

src/app/heroes/heroes-routing.module.ts (hero-detail-route)

When the user clicks the back button, the HeroDetailComponent constructs another link parameters array which it uses to navigate back to the HeroListComponent .

src/app/heroes/hero-detail.component.ts (gotoHeroes)

This array lacks a route parameter because you had no reason to send information to the HeroListComponent .

Now you have a reason. You'd like to send the id of the current hero with the navigation request so that the HeroListComponent can highlight that hero in its list. This is a nice-to-have feature; the list will display perfectly well without it.

Send the id with an object that contains an optional id parameter. For demonstration purposes, there's an extra junk parameter ( foo ) in the object that the HeroListComponent should ignore. Here's the revised navigation statement:

src/app/heroes/hero-detail.component.ts (go to heroes)

The application still works. Clicking "back" returns to the hero list view.

Look at the browser address bar.

It should look something like this, depending on where you run it:

The id value appears in the URL as ( ;id=15;foo=foo ), not in the URL path. The path for the "Heroes" route doesn't have an :id token.

The optional route parameters are not separated by "?" and "&" as they would be in the URL query string. They are separated by semicolons ";" This is matrix URL notation — something you may not have seen before.

Matrix URL notation is an idea first introduced in a 1996 proposal by the founder of the web, Tim Berners-Lee.

Although matrix notation never made it into the HTML standard, it is legal and it became popular among browser routing systems as a way to isolate parameters belonging to parent and child routes. The Router is such a system and provides support for the matrix notation across browsers.

The syntax may seem strange to you but users are unlikely to notice or care as long as the URL can be emailed and pasted into a browser address bar as this one can.

The list of heroes is unchanged. No hero row is highlighted.

The HeroListComponent isn't expecting any parameters at all and wouldn't know what to do with them. You can change that.

Previously, when navigating from the HeroListComponent to the HeroDetailComponent , you subscribed to the route params Observable and made it available to the HeroDetailComponent in the ActivatedRoute service. You injected that service in the constructor of the HeroDetailComponent .

This time you'll be navigating in the opposite direction, from the HeroDetailComponent to the HeroListComponent .

First you extend the router import statement to include the ActivatedRoute service symbol:

src/app/heroes/hero-list.component.ts (import)

Import the switchMap operator to perform an operation on the Observable of route parameters.

src/app/heroes/hero-list.component.ts (rxjs imports)

Then you inject the ActivatedRoute in the HeroListComponent constructor.

src/app/heroes/hero-list.component.ts (constructor and ngOnInit)

The ActivatedRoute.params property is an Observable of route parameters. The params emits new id values when the user navigates to the component. In ngOnInit you subscribe to those values, set the selectedId , and get the heroes.

All route/query parameters are strings. The (+) in front of the params['id'] expression is a JavaScript trick to convert the string to an integer.

Add an isSelected method that returns true when a hero's id matches the selected id .

src/app/heroes/hero-list.component.ts (isSelected)

Finally, update the template with a class binding to that isSelected method. The binding adds the selected CSS class when the method returns true and removes it when false . Look for it within the repeated <li> tag as shown here:

src/app/heroes/hero-list.component.ts (template)

When the user navigates from the heroes list to the "Magneta" hero and back, "Magneta" appears selected:

Selected List

The optional foo route parameter is harmless and continues to be ignored.

The heroes feature module is almost complete, but what is a feature without some smooth transitions?

This section shows you how to add some animations to the HeroDetailComponent .

Create an animations.ts file in the root src/app/ folder. The contents look like this:

src/app/animations.ts (excerpt)

This file does the following:

Imports the animation symbols that build the animation triggers, control state, and manage transitions between states.

Exports a constant named slideInDownAnimation set to an animation trigger named routeAnimation ; animated components will refer to this name.

Specifies the wildcard state , * , that matches any animation state that the route component is in.

Defines two transitions , one to ease the component in from the left of the screen as it enters the application view ( :enter ), the other to animate the component down as it leaves the application view ( :leave ).

You could create more triggers with different transitions for other route components. This trigger is sufficient for the current milestone.

Back in the HeroDetailComponent , import the slideInDownAnimation from './animations.ts . Add the HostBinding decorator to the imports from @angular/core ; you'll need it in a moment.

Add an animations array to the @Component metadata's that contains the slideInDownAnimation .

Then add three @HostBinding properties to the class to set the animation and styles for the route component's element.

src/app/heroes/hero-detail.component.ts (host bindings)

The '@routeAnimation' passed to the first @HostBinding matches the name of the slideInDownAnimation trigger , routeAnimation . Set the routeAnimation property to true because you only care about the :enter and :leave states.

The other two @HostBinding properties style the display and position of the component.

The HeroDetailComponent will ease in from the left when routed to and will slide down when navigating away.

Applying route animations to individual components works for a simple demo, but in a real life app, it is better to animate routes based on route paths .

  • Organize the app into feature areas .
  • Pass information along in route parameters and subscribe to them in the component.
  • Import the feature area NgModule into the AppModule .
  • Apply animations to the route component.

After these changes, the folder structure looks like this:

Milestone 4: Crisis center feature

It's time to add real features to the app's current placeholder crisis center.

Begin by imitating the heroes feature:

  • Delete the placeholder crisis center file.
  • Create an app/crisis-center folder.
  • Copy the files from app/heroes into the new crisis center folder.
  • In the new files, change every mention of "hero" to "crisis", and "heroes" to "crises".

You'll turn the CrisisService into a purveyor of mock crises instead of mock heroes:

src/app/crisis-center/crisis.service.ts (mock-crises)

The resulting crisis center is a foundation for introducing a new concept— child routing . You can leave Heroes in its current state as a contrast with the Crisis Center and decide later if the differences are worthwhile.

In keeping with the Separation of Concerns principle , changes to the Crisis Center won't affect the AppModule or any other feature's component.

This section shows you how to organize the crisis center to conform to the following recommended pattern for Angular applications:

  • Each feature area resides in its own folder.
  • Each feature has its own Angular feature module.
  • Each area has its own area root component.
  • Each area root component has its own router outlet and child routes.
  • Feature area routes rarely (if ever) cross with routes of other features.

If your app had many feature areas, the app component trees might look like this:

Component Tree

Add the following crisis-center.component.ts to the crisis-center folder:

src/app/crisis-center/crisis-center.component.ts (minus imports)

The CrisisCenterComponent has the following in common with the AppComponent :

  • It is the root of the crisis center area, just as AppComponent is the root of the entire application.
  • It is a shell for the crisis management feature area, just as the AppComponent is a shell to manage the high-level workflow.

Like most shells, the CrisisCenterComponent class is very simple, simpler even than AppComponent : it has no business logic, and its template has no links, just a title and <router-outlet> for the crisis center child views.

Unlike AppComponent , and most other components, it lacks a selector . It doesn't need one since you don't embed this component in a parent template, instead you use the router to navigate to it.

The CrisisCenterComponent is a routing component like the AppComponent . It has its own RouterOutlet and its own child routes.

Add the following crisis-center-home.component.ts to the crisis-center folder.

src/app/crisis-center/crisis-center-home.component.ts (minus imports)

Create a crisis-center-routing.module.ts file as you did the heroes-routing.module.ts file. This time, you define child routes within the parent crisis-center route.

src/app/crisis-center/crisis-center-routing.module.ts (Routes)

Notice that the parent crisis-center route has a children property with a single route containing the CrisisListComponent . The CrisisListComponent route also has a children array with two routes.

These two routes navigate to the crisis center child components, CrisisCenterHomeComponent and CrisisDetailComponent , respectively.

There are important differences in the way the router treats these child routes .

The router displays the components of these routes in the RouterOutlet of the CrisisCenterComponent , not in the RouterOutlet of the AppComponent shell.

The CrisisListComponent contains the crisis list and a RouterOutlet to display the Crisis Center Home and Crisis Detail route components.

The Crisis Detail route is a child of the Crisis List . Since the router reuses components by default, the Crisis Detail component will be re-used as you select different crises. In contrast, back in the Hero Detail route, the component was recreated each time you selected a different hero.

At the top level, paths that begin with / refer to the root of the application. But child routes extend the path of the parent route. With each step down the route tree, you add a slash followed by the route path, unless the path is empty .

Apply that logic to navigation within the crisis center for which the parent path is /crisis-center .

To navigate to the CrisisCenterHomeComponent , the full URL is /crisis-center ( /crisis-center + '' + '' ).

To navigate to the CrisisDetailComponent for a crisis with id=2 , the full URL is /crisis-center/2 ( /crisis-center + '' + '/2' ).

The absolute URL for the latter example, including the localhost origin, is

Here's the complete crisis-center-routing.module.ts file with its imports.

src/app/crisis-center/crisis-center-routing.module.ts (excerpt)

As with the HeroesModule , you must add the CrisisCenterModule to the imports array of the AppModule before the AppRoutingModule :

src/app/app.module.ts (import CrisisCenterModule)

Remove the initial crisis center route from the app-routing.module.ts . The feature routes are now provided by the HeroesModule and the CrisisCenter modules.

The app-routing.module.ts file retains the top-level application routes such as the default and wildcard routes.

src/app/app-routing.module.ts (v3)

While building out the crisis center feature, you navigated to the crisis detail route using an absolute path that begins with a slash .

The router matches such absolute paths to routes starting from the top of the route configuration.

You could continue to use absolute paths like this to navigate inside the Crisis Center feature, but that pins the links to the parent routing structure. If you changed the parent /crisis-center path, you would have to change the link parameters array.

You can free the links from this dependency by defining paths that are relative to the current URL segment. Navigation within the feature area remains intact even if you change the parent route path to the feature.

Here's an example:

The router supports directory-like syntax in a link parameters list to help guide route name lookup:

./ or no leading slash is relative to the current level.

../ to go up one level in the route path.

You can combine relative navigation syntax with an ancestor path. If you must navigate to a sibling route, you could use the ../<sibling> convention to go up one level, then over and down the sibling route path.

To navigate a relative path with the Router.navigate method, you must supply the ActivatedRoute to give the router knowledge of where you are in the current route tree.

After the link parameters array , add an object with a relativeTo property set to the ActivatedRoute . The router then calculates the target URL based on the active route's location.

Always specify the complete absolute path when calling router's navigateByUrl method.

Update the Crisis List onSelect method to use relative navigation so you don't have to start from the top of the route configuration.

You've already injected the ActivatedRoute that you need to compose the relative navigation path.

src/app/crisis-center/crisis-list.component.ts (constructor)

When you visit the Crisis Center , the ancestor path is /crisis-center , so you only need to add the id of the Crisis Center to the existing path.

src/app/crisis-center/crisis-list.component.ts (relative navigation)

If you were using a RouterLink to navigate instead of the Router service, you'd use the same link parameters array, but you wouldn't provide the object with the relativeTo property. The ActivatedRoute is implicit in a RouterLink directive.

src/app/crisis-center/crisis-list.component.ts (relative routerLink)

Update the gotoCrises method of the CrisisDetailComponent to navigate back to the Crisis Center list using relative path navigation.

src/app/crisis-center/crisis-detail.component.ts (relative navigation)

Notice that the path goes up a level using the ../ syntax. If the current crisis id is 3 , the resulting path back to the crisis list is /crisis-center/;id=3;foo=foo .

You decide to give users a way to contact the crisis center. When a user clicks a "Contact" button, you want to display a message in a popup view.

The popup should stay open, even when switching between pages in the application, until the user closes it by sending the message or canceling. Clearly you can't put the popup in the same outlet as the other pages.

Until now, you've defined a single outlet and you've nested child routes under that outlet to group routes together. The router only supports one primary unnamed outlet per template.

A template can also have any number of named outlets. Each named outlet has its own set of routes with their own components. Multiple outlets can be displaying different content, determined by different routes, all at the same time.

Add an outlet named "popup" in the AppComponent , directly below the unnamed outlet.

src/app/app.component.ts (outlets)

That's where a popup will go, once you learn how to route a popup component to it.

Named outlets are the targets of secondary routes .

Secondary routes look like primary routes and you configure them the same way. They differ in a few key respects.

  • They are independent of each other.
  • They work in combination with other routes.
  • They are displayed in named outlets.

Create a new component named ComposeMessageComponent in src/app/compose-message.component.ts . It displays a simple form with a header, an input box for the message, and two buttons, "Send" and "Cancel".

Contact popup

Here's the component and its template:

It looks about the same as any other component you've seen in this guide. There are two noteworthy differences.

Note that the send() method simulates latency by waiting a second before "sending" the message and closing the popup.

The closePopup() method closes the popup view by navigating to the popup outlet with a null . That's a peculiarity covered below .

As with other application components, you add the ComposeMessageComponent to the declarations of an NgModule . Do so in the AppModule .

Open the AppRoutingModule and add a new compose route to the appRoutes .

src/app/app-routing.module.ts (compose route)

The path and component properties should be familiar. There's a new property, outlet , set to 'popup' . This route now targets the popup outlet and the ComposeMessageComponent will display there.

The user needs a way to open the popup. Open the AppComponent and add a "Contact" link.

src/app/app.component.ts (contact-link)

Although the compose route is pinned to the "popup" outlet, that's not sufficient for wiring the route to a RouterLink directive. You have to specify the named outlet in a link parameters array and bind it to the RouterLink with a property binding.

The link parameters array contains an object with a single outlets property whose value is another object keyed by one (or more) outlet names. In this case there is only the "popup" outlet property and its value is another link parameters array that specifies the compose route.

You are in effect saying, when the user clicks this link, display the component associated with the compose route in the popup outlet .

This outlets object within an outer object was completely unnecessary when there was only one route and one unnamed outlet to think about.

The router assumed that your route specification targeted the unnamed primary outlet and created these objects for you.

Routing to a named outlet has revealed a previously hidden router truth: you can target multiple outlets with multiple routes in the same RouterLink directive.

You're not actually doing that here. But to target a named outlet, you must use the richer, more verbose syntax.

Navigate to the Crisis Center and click "Contact". you should see something like the following URL in the browser address bar.

The interesting part of the URL follows the ... :

  • The crisis-center is the primary navigation.
  • Parentheses surround the secondary route.
  • The secondary route consists of an outlet name ( popup ), a colon separator, and the secondary route path ( compose ).

Click the Heroes link and look at the URL again.

The primary navigation part has changed; the secondary route is the same.

The router is keeping track of two separate branches in a navigation tree and generating a representation of that tree in the URL.

You can add many more outlets and routes, at the top level and in nested levels, creating a navigation tree with many branches. The router will generate the URL to go with it.

You can tell the router to navigate an entire tree at once by filling out the outlets object mentioned above. Then pass that object inside a link parameters array to the router.navigate method.

Experiment with these possibilities at your leisure.

As you've learned, a component in an outlet persists until you navigate away to a new component. Secondary outlets are no different in this regard.

Each secondary outlet has its own navigation, independent of the navigation driving the primary outlet. Changing a current route that displays in the primary outlet has no effect on the popup outlet. That's why the popup stays visible as you navigate among the crises and heroes.

Clicking the "send" or "cancel" buttons does clear the popup view. To see how, look at the closePopup() method again:

src/app/compose-message.component.ts (closePopup)

It navigates imperatively with the Router.navigate() method, passing in a link parameters array .

Like the array bound to the Contact RouterLink in the AppComponent , this one includes an object with an outlets property. The outlets property value is another object with outlet names for keys. The only named outlet is 'popup' .

This time, the value of 'popup' is null . That's not a route, but it is a legitimate value. Setting the popup RouterOutlet to null clears the outlet and removes the secondary popup route from the current URL.

Milestone 5: Route guards

At the moment, any user can navigate anywhere in the application anytime . That's not always the right thing to do.

  • Perhaps the user is not authorized to navigate to the target component.
  • Maybe the user must login ( authenticate ) first.
  • Maybe you should fetch some data before you display the target component.
  • You might want to save pending changes before leaving a component.
  • You might ask the user if it's OK to discard pending changes rather than save them.

You can add guards to the route configuration to handle these scenarios.

A guard's return value controls the router's behavior:

  • If it returns true , the navigation process continues.
  • If it returns false , the navigation process stops and the user stays put.

The guard can also tell the router to navigate elsewhere, effectively canceling the current navigation.

The guard might return its boolean answer synchronously. But in many cases, the guard can't produce an answer synchronously. The guard could ask the user a question, save changes to the server, or fetch fresh data. These are all asynchronous operations.

Accordingly, a routing guard can return an Observable<boolean> or a Promise<boolean> and the router will wait for the observable to resolve to true or false .

The router supports multiple kinds of guards:

CanActivate to mediate navigation to a route.

CanActivateChild() to mediate navigation to a child route.

CanDeactivate to mediate navigation away from the current route.

Resolve to perform route data retrieval before route activation.

CanLoad to mediate navigation to a feature module loaded asynchronously .

You can have multiple guards at every level of a routing hierarchy. The router checks the CanDeactivate() and CanActivateChild() guards first, from the deepest child route to the top. Then it checks the CanActivate() guards from the top down to the deepest child route. If the feature module is loaded asynchronously, the CanLoad() guard is checked before the module is loaded. If any guard returns false, pending guards that have not completed will be canceled, and the entire navigation is canceled.

There are several examples over the next few sections.

Applications often restrict access to a feature area based on who the user is. You could permit access only to authenticated users or to users with a specific role. You might block or limit access until the user's account is activated.

The CanActivate guard is the tool to manage these navigation business rules.

Add an admin feature module

In this next section, you'll extend the crisis center with some new administrative features. Those features aren't defined yet. But you can start by adding a new feature module named AdminModule .

Create an admin folder with a feature module file, a routing configuration file, and supporting components.

The admin feature file structure looks like this:

The admin feature module contains the AdminComponent used for routing within the feature module, a dashboard route and two unfinished components to manage crises and heroes.

Since the admin dashboard RouterLink is an empty path route in the AdminComponent , it is considered a match to any route within the admin feature area. You only want the Dashboard link to be active when the user visits that route. Adding an additional binding to the Dashboard routerLink, [routerLinkActiveOptions]="{ exact: true }" , marks the ./ link as active when the user navigates to the /admin URL and not when navigating to any of the child routes.

The initial admin routing configuration:

src/app/admin/admin-routing.module.ts (admin routing)

Looking at the child route under the AdminComponent , there is a path and a children property but it's not using a component . You haven't made a mistake in the configuration. You've defined a component-less route.

The goal is to group the Crisis Center management routes under the admin path. You don't need a component to do it. A component-less route makes it easier to guard child routes .

Next, import the AdminModule into app.module.ts and add it to the imports array to register the admin routes.

src/app/app.module.ts (admin module)

Add an "Admin" link to the AppComponent shell so that users can get to this feature.

src/app/app.component.ts (template)

Currently every route within the Crisis Center is open to everyone. The new admin feature should be accessible only to authenticated users.

You could hide the link until the user logs in. But that's tricky and difficult to maintain.

Instead you'll write a CanActivate() guard to redirect anonymous users to the login page when they try to enter the admin area.

This is a general purpose guard—you can imagine other features that require authenticated users—so you create an auth-guard.service.ts in the application root folder.

At the moment you're interested in seeing how guards work so the first version does nothing useful. It simply logs to console and returns true immediately, allowing navigation to proceed:

src/app/auth-guard.service.ts (excerpt)

Next, open admin-routing.module.ts , import the AuthGuard class, and update the admin route with a CanActivate() guard property that references it:

src/app/admin/admin-routing.module.ts (guarded admin route)

The admin feature is now protected by the guard, albeit protected poorly.

Make the AuthGuard at least pretend to authenticate.

The AuthGuard should call an application service that can login a user and retain information about the current user. Here's a demo AuthService :

src/app/auth.service.ts (excerpt)

Although it doesn't actually log in, it has what you need for this discussion. It has an isLoggedIn flag to tell you whether the user is authenticated. Its login method simulates an API call to an external service by returning an Observable that resolves successfully after a short pause. The redirectUrl property will store the attempted URL so you can navigate to it after authenticating.

Revise the AuthGuard to call it.

src/app/auth-guard.service.ts (v2)

Notice that you inject the AuthService and the Router in the constructor. You haven't provided the AuthService yet but it's good to know that you can inject helpful services into routing guards.

This guard returns a synchronous boolean result. If the user is logged in, it returns true and the navigation continues.

The ActivatedRouteSnapshot contains the future route that will be activated and the RouterStateSnapshot contains the future RouterState of the application, should you pass through the guard check.

If the user is not logged in, you store the attempted URL the user came from using the RouterStateSnapshot.url and tell the router to navigate to a login page—a page you haven't created yet. This secondary navigation automatically cancels the current navigation; checkLogin() returns false just to be clear about that.

Add the LoginComponent

You need a LoginComponent for the user to log in to the app. After logging in, you'll redirect to the stored URL if available, or use the default URL. There is nothing new about this component or the way you wire it into the router configuration.

Register a /login route in the login-routing.module.ts and add the necessary providers to the providers array. In app.module.ts , import the LoginComponent and add it to the AppModule declarations . Import and add the LoginRoutingModule to the AppModule imports as well.

Guards and the service providers they require must be provided at the module-level. This allows the Router access to retrieve these services from the Injector during the navigation process. The same rule applies for feature modules loaded asynchronously .

You can also protect child routes with the CanActivateChild guard. The CanActivateChild guard is similar to the CanActivate guard. The key difference is that it runs before any child route is activated.

You protected the admin feature module from unauthorized access. You should also protect child routes within the feature module.

Extend the AuthGuard to protect when navigating between the admin routes. Open auth-guard.service.ts and add the CanActivateChild interface to the imported tokens from the router package.

Next, implement the CanActivateChild method which takes the same arguments as the CanActivate method: an ActivatedRouteSnapshot and RouterStateSnapshot . The CanActivateChild method can return an Observable<boolean> or Promise<boolean> for async checks and a boolean for sync checks. This one returns a boolean :

Add the same AuthGuard to the component-less admin route to protect all other child routes at one time instead of adding the AuthGuard to each route individually.

src/app/admin/admin-routing.module.ts (excerpt)

Back in the "Heroes" workflow, the app accepts every change to a hero immediately without hesitation or validation.

In the real world, you might have to accumulate the users changes. You might have to validate across fields. You might have to validate on the server. You might have to hold changes in a pending state until the user confirms them as a group or cancels and reverts all changes.

What do you do about unapproved, unsaved changes when the user navigates away? You can't just leave and risk losing the user's changes; that would be a terrible experience.

It's better to pause and let the user decide what to do. If the user cancels, you'll stay put and allow more changes. If the user approves, the app can save.

You still might delay navigation until the save succeeds. If you let the user move to the next screen immediately and the save were to fail (perhaps the data are ruled invalid), you would lose the context of the error.

You can't block while waiting for the server—that's not possible in a browser. You need to stop the navigation while you wait, asynchronously, for the server to return with its answer.

You need the CanDeactivate guard.

The sample application doesn't talk to a server. Fortunately, you have another way to demonstrate an asynchronous router hook.

Users update crisis information in the CrisisDetailComponent . Unlike the HeroDetailComponent , the user changes do not update the crisis entity immediately. Instead, the app updates the entity when the user presses the Save button and discards the changes when the user presses the Cancel button.

Both buttons navigate back to the crisis list after save or cancel.

src/app/crisis-center/crisis-detail.component.ts (cancel and save methods)

What if the user tries to navigate away without saving or canceling? The user could push the browser back button or click the heroes link. Both actions trigger a navigation. Should the app save or cancel automatically?

This demo does neither. Instead, it asks the user to make that choice explicitly in a confirmation dialog box that waits asynchronously for the user's answer .

You could wait for the user's answer with synchronous, blocking code. The app will be more responsive—and can do other work—by waiting for the user's answer asynchronously. Waiting for the user asynchronously is like waiting for the server asynchronously.

The DialogService , provided in the AppModule for app-wide use, does the asking.

It returns a promise that resolves when the user eventually decides what to do: either to discard changes and navigate away ( true ) or to preserve the pending changes and stay in the crisis editor ( false ).

Create a guard that checks for the presence of a canDeactivate method in a component—any component. The CrisisDetailComponent will have this method. But the guard doesn't have to know that. The guard shouldn't know the details of any component's deactivation method. It need only detect that the component has a canDeactivate() method and call it. This approach makes the guard reusable.

src/app/can-deactivate-guard.service.ts

Alternatively, you could make a component-specific CanDeactivate guard for the CrisisDetailComponent . The canDeactivate() method provides you with the current instance of the component , the current ActivatedRoute , and RouterStateSnapshot in case you needed to access some external information. This would be useful if you only wanted to use this guard for this component and needed to get the component's properties or confirm whether the router should allow navigation away from it.

src/app/can-deactivate-guard.service.ts (component-specific)

Looking back at the CrisisDetailComponent , it implements the confirmation workflow for unsaved changes.

src/app/crisis-center/crisis-detail.component.ts (excerpt)

Notice that the canDeactivate method can return synchronously; it returns true immediately if there is no crisis or there are no pending changes. But it can also return a Promise or an Observable and the router will wait for that to resolve to truthy (navigate) or falsy (stay put).

Add the Guard to the crisis detail route in crisis-center-routing.module.ts using the canDeactivate array.

src/app/crisis-center/crisis-center-routing.module.ts (can deactivate guard)

Add the Guard to the main AppRoutingModule providers array so the Router can inject it during the navigation process.

Now you have given the user a safeguard against unsaved changes.

In the Hero Detail and Crisis Detail , the app waited until the route was activated to fetch the respective hero or crisis.

This worked well, but there's a better way. If you were using a real world API, there might be some delay before the data to display is returned from the server. You don't want to display a blank component while waiting for the data.

It's preferable to pre-fetch data from the server so it's ready the moment the route is activated. This also allows you to handle errors before routing to the component. There's no point in navigating to a crisis detail for an id that doesn't have a record. It'd be better to send the user back to the Crisis List that shows only valid crisis centers.

In summary, you want to delay rendering the routed component until all necessary data have been fetched.

You need a resolver .

At the moment, the CrisisDetailComponent retrieves the selected crisis. If the crisis is not found, it navigates back to the crisis list view.

The experience might be better if all of this were handled first, before the route is activated. A CrisisDetailResolver service could retrieve a Crisis or navigate away if the Crisis does not exist before activating the route and creating the CrisisDetailComponent .

Create the crisis-detail-resolver.service.ts file within the Crisis Center feature area.

src/app/crisis-center/crisis-detail-resolver.service.ts

Take the relevant parts of the crisis retrieval logic in CrisisDetailComponent.ngOnInit and move them into the CrisisDetailResolver . Import the Crisis model, CrisisService , and the Router so you can navigate elsewhere if you can't fetch the crisis.

Be explicit. Implement the Resolve interface with a type of Crisis .

Inject the CrisisService and Router and implement the resolve() method. That method could return a Promise , an Observable , or a synchronous return value.

The CrisisService.getCrisis method returns a promise. Return that promise to prevent the route from loading until the data is fetched. If it doesn't return a valid Crisis , navigate the user back to the CrisisListComponent , canceling the previous in-flight navigation to the CrisisDetailComponent .

Import this resolver in the crisis-center-routing.module.ts and add a resolve object to the CrisisDetailComponent route configuration.

Remember to add the CrisisDetailResolver service to the CrisisCenterRoutingModule 's providers array.

src/app/crisis-center/crisis-center-routing.module.ts (resolver)

The CrisisDetailComponent should no longer fetch the crisis. Update the CrisisDetailComponent to get the crisis from the ActivatedRoute.data.crisis property instead; that's where you said it should be when you re-configured the route. It will be there when the CrisisDetailComponent ask for it.

src/app/crisis-center/crisis-detail.component.ts (ngOnInit v2)

Two critical points

The router's Resolve interface is optional. The CrisisDetailResolver doesn't inherit from a base class. The router looks for that method and calls it if found.

Rely on the router to call the resolver. Don't worry about all the ways that the user could navigate away. That's the router's job. Write this class and let the router take it from there.

The relevant Crisis Center code for this milestone follows.

In the route parameters example, you only dealt with parameters specific to the route, but what if you wanted optional parameters available to all routes? This is where query parameters come into play.

Fragments refer to certain elements on the page identified with an id attribute.

Update the AuthGuard to provide a session_id query that will remain after navigating to another route.

Add an anchor element so you can jump to a certain point on the page.

Add the NavigationExtras object to the router.navigate method that navigates you to the /login route.

src/app/auth-guard.service.ts (v3)

You can also preserve query parameters and fragments across navigations without having to provide them again when navigating. In the LoginComponent , you'll add an object as the second argument in the router.navigate function and provide the preserveQueryParams and preserveFragment to pass along the current query parameters and fragment to the next route.

src/app/login.component.ts (preserve)

Since you'll be navigating to the Admin Dashboard route after logging in, you'll update it to handle the query parameters and fragment.

src/app/admin/admin-dashboard.component.ts (v2)

Query Parameters and Fragments are also available through the ActivatedRoute service. Just like route parameters , the query parameters and fragments are provided as an Observable . The updated Crisis Admin component feeds the Observable directly into the template using the AsyncPipe .

Now, you can click on the Admin button, which takes you to the Login page with the provided query params and fragment . After you click the login button, notice that you have been redirected to the Admin Dashboard page with the query params and fragment still intact.

You can use these persistent bits of information for things that need to be provided across pages like authentication tokens or session ids.

The query params and fragment can also be preserved using a RouterLink with the preserveQueryParams and preserveFragment bindings respectively.

Milestone 6: Asynchronous routing

As you've worked through the milestones, the application has naturally gotten larger. As you continue to build out feature areas, the overall application size will continue to grow. At some point you'll reach a tipping point where the application takes long time to load.

How do you combat this problem? With asynchronous routing, which loads feature modules lazily , on request. Lazy loading has multiple benefits.

  • You can load feature areas only when requested by the user.
  • You can speed up load time for users that only visit certain areas of the application.
  • You can continue expanding lazy loaded feature areas without increasing the size of the initial load bundle.

You're already made part way there. By organizing the application into modules— AppModule , HeroesModule , AdminModule and CrisisCenterModule —you have natural candidates for lazy loading.

Some modules, like AppModule , must be loaded from the start. But others can and should be lazy loaded. The AdminModule , for example, is needed by a few authorized users, so you should only load it when requested by the right people.

Lazy Loading route configuration

Change the admin path in the admin-routing.module.ts from 'admin' to an empty string, '' , the empty path .

The Router supports empty path routes; use them to group routes together without adding any additional path segments to the URL. Users will still visit /admin and the AdminComponent still serves as the Routing Component containing child routes.

Open the AppRoutingModule and add a new admin route to its appRoutes array.

Give it a loadChildren property (not a children property!), set to the address of the AdminModule . The address is the AdminModule file location (relative to the app root), followed by a # separator, followed by the name of the exported module class, AdminModule .

app-routing.module.ts (load children)

When the router navigates to this route, it uses the loadChildren string to dynamically load the AdminModule . Then it adds the AdminModule routes to its current route configuration. Finally, it loads the requested route to the destination admin component.

The lazy loading and re-configuration happen just once, when the route is first requested; the module and routes are available immediately for subsequent requests.

Angular provides a built-in module loader that supports SystemJS to load modules asynchronously. If you were using another bundling tool, such as Webpack, you would use the Webpack mechanism for asynchronously loading modules.

Take the final step and detach the admin feature set from the main application. The root AppModule must neither load nor reference the AdminModule or its files.

In app.module.ts , remove the AdminModule import statement from the top of the file and remove the AdminModule from the Angular module's imports array.

You're already protecting the AdminModule with a CanActivate guard that prevents unauthorized users from accessing the admin feature area. It redirects to the login page if the user is not authorized.

But the router is still loading the AdminModule even if the user can't visit any of its components. Ideally, you'd only load the AdminModule if the user is logged in.

Add a CanLoad guard that only loads the AdminModule once the user is logged in and attempts to access the admin feature area.

The existing AuthGuard already has the essential logic in its checkLogin() method to support the CanLoad guard.

Open auth-guard.service.ts . Import the CanLoad interface from @angular/router . Add it to the AuthGuard class's implements list. Then implement canLoad as follows:

src/app/auth-guard.service.ts (CanLoad guard)

The router sets the canLoad() method's route parameter to the intended destination URL. The checkLogin() method redirects to that URL once the user has logged in.

Now import the AuthGuard into the AppRoutingModule and add the AuthGuard to the canLoad array for the admin route. The completed admin route looks like this:

app-routing.module.ts (lazy admin route)

You've learned how to load modules on-demand. You can also load modules asynchronously with preloading .

This may seem like what the app has been doing all along. Not quite. The AppModule is loaded when the application starts; that's eager loading. Now the AdminModule loads only when the user clicks on a link; that's lazy loading.

Preloading is something in between. Consider the Crisis Center . It isn't the first view that a user sees. By default, the Heroes are the first view. For the smallest initial payload and fastest launch time, you should eagerly load the AppModule and the HeroesModule .

You could lazy load the Crisis Center . But you're almost certain that the user will visit the Crisis Center within minutes of launching the app. Ideally, the app would launch with just the AppModule and the HeroesModule loaded and then, almost immediately, load the CrisisCenterModule in the background. By the time the user navigates to the Crisis Center , its module will have been loaded and ready to go.

That's preloading .

After each successful navigation, the router looks in its configuration for an unloaded module that it can preload. Whether it preloads a module, and which modules it preloads, depends upon the preload strategy .

The Router offers two preloading strategies out of the box:

  • No preloading at all which is the default. Lazy loaded feature areas are still loaded on demand.
  • Preloading of all lazy loaded feature areas.

Out of the box, the router either never preloads, or preloads every lazy load module. The Router also supports custom preloading strategies for fine control over which modules to preload and when.

In this next section, you'll update the CrisisCenterModule to load lazily by default and use the PreloadAllModules strategy to load it (and all other lazy loaded modules) as soon as possible.

Update the route configuration to lazy load the CrisisCenterModule . Take the same steps you used to configure AdminModule for lazy load.

Change the crisis-center path in the CrisisCenterRoutingModule to an empty string.

Add a crisis-center route to the AppRoutingModule .

Set the loadChildren string to load the CrisisCenterModule .

Remove all mention of the CrisisCenterModule from app.module.ts .

Here are the updated modules before enabling preload :

You could try this now and confirm that the CrisisCenterModule loads after you click the "Crisis Center" button.

To enable preloading of all lazy loaded modules, import the PreloadAllModules token from the Angular router package.

The second argument in the RouterModule.forRoot method takes an object for additional configuration options. The preloadingStrategy is one of those options. Add the PreloadAllModules token to the forRoot call:

src/app/app-routing.module.ts (preload all)

This tells the Router preloader to immediately load all lazy loaded routes (routes with a loadChildren property).

When you visit http://localhost:3000 , the /heroes route loads immediately upon launch and the router starts loading the CrisisCenterModule right after the HeroesModule loads.

Surprisingly, the AdminModule does not preload. Something is blocking it.

The PreloadAllModules strategy does not load feature areas protected by a CanLoad guard. This is by design.

You added a CanLoad guard to the route in the AdminModule a few steps back to block loading of that module until the user is authorized. That CanLoad guard takes precedence over the preload strategy.

If you want to preload a module and guard against unauthorized access, drop the canLoad guard and rely on the CanActivate guard alone.

Preloading every lazy loaded modules works well in many situations, but it isn't always the right choice, especially on mobile devices and over low bandwidth connections. You may choose to preload only certain feature modules, based on user metrics and other business and technical factors.

You can control what and how the router preloads with a custom preloading strategy.

In this section, you'll add a custom strategy that only preloads routes whose data.preload flag is set to true . Recall that you can add anything to the data property of a route.

Set the data.preload flag in the crisis-center route in the AppRoutingModule .

src/app/app-routing.module.ts (route data preload)

Add a new file to the project called selective-preloading-strategy.ts and define a SelectivePreloadingStrategy service class as follows:

src/app/selective-preloading-strategy.ts (excerpt)

SelectivePreloadingStrategy implements the PreloadingStrategy , which has one method, preload .

The router calls the preload method with two arguments:

  • The route to consider.
  • A loader function that can load the routed module asynchronously.

An implementation of preload must return an Observable . If the route should preload, it returns the observable returned by calling the loader function. If the route should not preload, it returns an Observable of null .

In this sample, the preload method loads the route if the route's data.preload flag is truthy.

It also has a side-effect. SelectivePreloadingStrategy logs the path of a selected route in its public preloadedModules array.

Shortly, you'll extend the AdminDashboardComponent to inject this service and display its preloadedModules array.

But first, make a few changes to the AppRoutingModule .

  • Import SelectivePreloadingStrategy into AppRoutingModule .
  • Replace the PreloadAllModules strategy in the call to forRoot with this SelectivePreloadingStrategy .
  • Add the SelectivePreloadingStrategy strategy to the AppRoutingModule providers array so it can be injected elsewhere in the app.

Now edit the AdminDashboardComponent to display the log of preloaded routes.

  • Import the SelectivePreloadingStrategy (it's a service).
  • Inject it into the dashboard's constructor.
  • Update the template to display the strategy service's preloadedModules array.

When you're done it looks like this.

src/app/admin/admin-dashboard.component.ts (preloaded modules)

Once the application loads the initial route, the CrisisCenterModule is preloaded. Verify this by logging in to the Admin feature area and noting that the crisis-center is listed in the Preloaded Modules . It's also logged to the browser's console.

You put a lot of effort into configuring the router in several routing module files and were careful to list them in the proper order . Are routes actually evaluated as you planned? How is the router really configured?

You can inspect the router's current configuration any time by injecting it and examining its config property. For example, update the AppModule as follows and look in the browser console window to see the finished route configuration.

src/app/app.module.ts (inspect the router config)

The balance of this guide is a set of appendices that elaborate some of the points you covered quickly above.

The appendix material isn't essential. Continued reading is for the curious.

A link parameters array holds the following ingredients for router navigation:

  • The path of the route to the destination component.
  • Required and optional route parameters that go into the route URL.

You can bind the RouterLink directive to such an array like this:

You've written a two element array when specifying a route parameter like this:

You can provide optional route parameters in an object like this:

These three examples cover the need for an app with one level routing. The moment you add a child router, such as the crisis center, you create new link array possibilities.

Recall that you specified a default child route for the crisis center so this simple RouterLink is fine.

Parse it out.

  • The first item in the array identifies the parent route ( /crisis-center ).
  • There are no parameters for this parent route so you're done with it.
  • There is no default for the child route so you need to pick one.
  • You're navigating to the CrisisListComponent , whose route path is / , but you don't need to explicitly add the slash.
  • Voilà! ['/crisis-center'] .

Take it a step further. Consider the following router link that navigates from the root of the application down to the Dragon Crisis :

  • The second item identifies the child route details about a particular crisis ( /:id ).
  • The details child route requires an id route parameter.
  • You added the id of the Dragon Crisis as the second item in the array ( 1 ).
  • The resulting path is /crisis-center/1 .

If you wanted to, you could redefine the AppComponent template with Crisis Center routes exclusively:

In sum, you can write applications with one, two or more levels of routing. The link parameters array affords the flexibility to represent any routing depth and any legal sequence of route paths, (required) router parameters, and (optional) route parameter objects.

When the router navigates to a new component view, it updates the browser's location and history with a URL for that view. This is a strictly local URL. The browser shouldn't send this URL to the server and should not reload the page.

Modern HTML5 browsers support history.pushState , a technique that changes a browser's location and history without triggering a server page request. The router can compose a "natural" URL that is indistinguishable from one that would otherwise require a page load.

Here's the Crisis Center URL in this "HTML5 pushState" style:

Older browsers send page requests to the server when the location URL changes unless the change occurs after a "#" (called the "hash"). Routers can take advantage of this exception by composing in-application route URLs with hashes. Here's a "hash URL" that routes to the Crisis Center .

The router supports both styles with two LocationStrategy providers:

  • PathLocationStrategy —the default "HTML5 pushState" style.
  • HashLocationStrategy —the "hash URL" style.

The RouterModule.forRoot function sets the LocationStrategy to the PathLocationStrategy , making it the default strategy. You can switch to the HashLocationStrategy with an override during the bootstrapping process if you prefer it.

Learn about providers and the bootstrap process in the Dependency Injection guide .

Which strategy is best?

You must choose a strategy and you need to make the right call early in the project. It won't be easy to change later once the application is in production and there are lots of application URL references in the wild.

Almost all Angular projects should use the default HTML5 style. It produces URLs that are easier for users to understand. And it preserves the option to do server-side rendering later.

Rendering critical pages on the server is a technique that can greatly improve perceived responsiveness when the app first loads. An app that would otherwise take ten or more seconds to start could be rendered on the server and delivered to the user's device in less than a second.

This option is only available if application URLs look like normal web URLs without hashes (#) in the middle.

Stick with the default unless you have a compelling reason to resort to hash routes.

HTML5 URLs and the <base href>

While the router uses the HTML5 pushState style by default, you must configure that strategy with a base href .

The preferred way to configure the strategy is to add a <base href> element tag in the <head> of the index.html .

Without that tag, the browser may not be able to load resources (images, CSS, scripts) when "deep linking" into the app. Bad things could happen when someone pastes an application link into the browser's address bar or clicks such a link in an email.

Some developers may not be able to add the <base> element, perhaps because they don't have access to <head> or the index.html .

Those developers may still use HTML5 URLs by taking two remedial steps:

  • Provide the router with an appropriate APP_BASE_HREF value.
  • Use root URLs for all web resources: CSS, images, scripts, and template HTML files.

HashLocationStrategy

You can go old-school with the HashLocationStrategy by providing the useHash: true in an object as the second argument of the RouterModule.forRoot in the AppModule .

src/app/app.module.ts (hash URL strategy)

Cory Rylan

My name is Cory Rylan , Google Developer Expert , Speaker, Software Developer . Building Design Systems and Web Components .

Introduction to Angular Routing

- 6 minutes

This article has been updated to the latest version Angular 17 and tested with Angular 16. The content is likely still applicable for all Angular 2 + versions.

Angular brings many improved modules to the Angular ecosystem including a new router called the Component Router. The Component Router is a highly configurable and feature packed router. Features included are standard view routing, nested child routes, named routes, and route parameters. This post we will cover standard routing, route parameters and nested child routes. With these basics we can build a great navigation experience for users that is easy to reason about.

Basic Routing

So now lets take a look at what our first rendered view will look like.

We will start with single app component that has two routes. We will have a home view and a about view. Lets take a look at these two components first.

So we can see that our two components are very simple just displaying some text. Now lets look at our app component and see how we can use the new Router to route between these two components.

So lets walk through our app component step by step and see what this code is doing.

So the first part is the [routerLink] . This directive generates our link based on the route path. The second part is the router-outlet , this is the location where Angular will insert the component we want to route to on the view. Next lets take a look at our route config file app.routes.ts .

Our route config defines all the routes in our application. The first route is our default home route. The second one is our AboutComponent . The path value is the path that we referenced in our template. We export our routes to be added to our App Module.

Our AppModule is where we create a new @NgModule . A NgModule is a way to bundle components, services and pipes into a single module for Angular to consume. So we will register all of our components to our AppModule then boostrap the module like below.

Now that our routes are registered we have our standard routing working. Next lets look at nested routes.

Nested Child Routes

Child/Nested routing is a powerful new feature in the new Angular router. We can think of our application as a tree structure, components nested in more components. We can think the same way with our routes and URLs.

So we have the following routes, / and /about . Maybe our about page is extensive and there are a couple of different views we would like to display as well. The URLs would look something like /about and /about/item . The first route would be the default about page but the more route would offer another view with more details.

So lets take a look at what that looks like rendered out.

So as above we can see the About view has its own router-outlet highlighted in blue. The about view also has its own links that navigate between two nested about child components. We can think of this as a tree structure.

So lets take a look at our About components.

So our About template looks very similar to the App component template. Our about component has two child routes, about/ and about/item These pull in two simple components that just display the rendered text above. Notice our route paths start at the root of the about component. The rendered URLs would be /about/ and /about/item . Note remember to add our newly created components to the declarations in our AppModule . Lets now take a look at the updated route config.

Notice our path in our About component is now relative for all the child components. Next we will learn how to dynamically change data in our component via route parameters.

Route Parameters

Building on top of our demo app we are now going to add a component that takes in a route parameter. Route parameters allow us to pass values in our url to our component so we can dynamically change our view content. So in our example we will have a route that can take an id and then display it on our AboutItemComponent component. So lets take a look at what the rendered output would be.

Our URLs would be the following: /about/ , /about/item/1 , and /about/item 2 . We can swap out any number in our URL and our item component can pull that value out and display it in the view. Let's take a look at the code for the root about component.

So the first part in our about template our new [routerLink] 's have a second parameter of the id value ex: ['/about/item', 2] . This allows us to pass in parameters to the router. For now we are just passing in a number. Now lets take a look at the updated route config.

Now taking a look at the item route we now have 'item/:id' the : denotes that this is a route parameter and the r outer should get the value in the URL. So now lets take a look at the AboutItemComponent and see how we can get the id from the URL.

We import the ActivatedRoute class and inject it into our component. The parameters are wrapped in an Observable that will push the current route parameter value whenever the parameter is updated. We subscribe for any changes. When a new value is received we set the value to a property on our template. We could just as easily taken this value as an ID to retrieve some data from a API. We capture the subscription in a property so when the component is destroyed we unsubscribe preventing any memory leaks.

Using Observables to get route params works well when the component persists on the same screen without having to be destroyed and recreated each time. If you are certain your component will be destroyed before a new parameter is updated you can use the snapshot api option documented here .

So now lets take a look at our diagram of our application's routes.

So we learned how to do basic routing between components, child/nested routing and route parameters. With these features mastered you can quickly build large scalable Angular apps in no time. As the new Release Candidate router is documented I will update this post accordingly. Take a look at full working demo in the link below.

Angular Form Essentials

Angular Form Essentials

Forms can be complicated. Become an expert using Angular Reactive Forms and RxJS . Learn to manage async validation , build accessible , and reusable custom inputs . Get a jump start on building Angular Forms today!

No spam. Short occasional updates on Web Development articles, videos, and new courses in your inbox.

Related Posts

Creating dynamic tables in angular.

Learn how to easily create HTML tables in Angular from dynamic data sources.

Reusable Component Patterns - Default Slots

Learn about how to use default slots in Web Components for a more flexible API design.

Reusable Component Anti-Patterns - Semantic Obfuscation

Learn about UI Component API design and one of the common anti-patterns, Semantic Obfuscation.

The router is responsible for mapping URLs to components.

childRouter

Constructs a child router. You probably don't need to use this unless you're writing a reusable component.

registerViewPort

Register an object to notify of route changes. You probably don't need to use this unless you're writing a reusable component.

Update the routing configuation and trigger a navigation.

For more, see the configuration guide.

Navigate to a URL. Returns the cannonical URL for the route navigated to.

Navigates to either the last URL successfully naviagted to, or the last URL requested if the router has yet to successfully navigate. You shouldn't need to use this API very often.

generate a URL from a component name and optional map of parameters. The URL is relative to the app's base href.

Router tutorial: tour of heroes

  • 1.1 Objectives
  • 1.2 Prerequisites
  • 1.3 The sample application in action
  • 1.4.1 Define Routes
  • 1.4.2 Register Router and Routes
  • 1.4.3 Add the Router Outlet
  • 1.4.4 Define a Wildcard route
  • 1.4.5 Set up redirects
  • 1.4.6 Milestone 1 wrap up
  • 1.5.1 Integrate routing with your app
  • 1.5.2 Refactor the routing configuration into a routing module
  • 1.5.3 Benefits of a routing module
  • 1.6.1.1 Hero feature routing requirements
  • 1.6.1.2 Remove duplicate hero routes
  • 1.6.1.3 Remove heroes declarations
  • 1.6.2 Module import order
  • 1.6.3.1 Route definition with a parameter
  • 1.6.3.2 Setting the route parameters in the list view
  • 1.6.4.1 ParamMap API
  • 1.6.4.2 Observable paramMap and component reuse
  • 1.6.4.3 snapshot: the no-observable alternative
  • 1.6.5.1 Route Parameters: Required or optional?
  • 1.6.5.2 Heroes list: optionally selecting a hero
  • 1.6.6 Route parameters in the ActivatedRoute service
  • 1.6.7 Adding routable animations
  • 1.6.8 Milestone 3 wrap up
  • 1.7.1 A crisis center with child routes
  • 1.7.2 Child routing component
  • 1.7.3 Child route configuration
  • 1.7.4 Import crisis center module into the AppModule routes
  • 1.7.5 Relative navigation
  • 1.7.6 Navigate to crisis list with a relative URL
  • 1.7.7.1 Secondary routes
  • 1.7.7.2 Add a secondary route
  • 1.7.7.3 Secondary route navigation: merging routes during navigation
  • 1.7.7.4 Clearing secondary routes
  • 1.8.1.1.1 Component-less route: grouping routes without a component
  • 1.8.1.2 Guard the admin feature
  • 1.8.1.3 Authenticate with AuthGuard
  • 1.8.1.4 Add the LoginComponent
  • 1.8.2 CanActivateChild: guarding child routes
  • 1.8.3.1 Cancel and save
  • 1.8.4.1 Fetch data before navigating
  • 1.8.5 Query parameters and fragments
  • 1.9.1 Lazy Loading route configuration
  • 1.9.2 CanLoad: guarding unauthorized loading of feature modules
  • 1.9.3.1 How preloading works
  • 1.9.3.2 Lazy load the crisis center
  • 1.9.3.3 CanLoad blocks preload
  • 1.9.4 Custom Preloading Strategy
  • 1.9.5.1 Changing /heroes to /superheroes
  • 1.9.6 Inspect the router's configuration
  • 1.10 Final app

This tutorial provides an extensive overview of the Angular router. In this tutorial, you will build upon a basic router configuration to explore features such as child routes, route parameters, lazy load NgModules, guard routes, and preloading data to improve the user experience.

For a working example of the final version of the app, see the .

This guide describes development of a multi-page routed sample application. Along the way, it highlights key features of the router such as:

  • Organizing the application features into modules.
  • Navigating to a component ( Heroes link to "Heroes List").
  • Including a route parameter (passing the Hero id while routing to the "Hero Detail").
  • Child routes (the Crisis Center has its own routes).
  • The CanActivate guard (checking route access).
  • The CanActivateChild guard (checking child route access).
  • The CanDeactivate guard (ask permission to discard unsaved changes).
  • The Resolve guard (pre-fetching route data).
  • Lazy loading an NgModule .
  • The CanLoad guard (check before loading feature module assets).

This guide proceeds as a sequence of milestones as if you were building the app step-by-step, but assumes you are familiar with basic Angular concepts . For a general introduction to angular, see the Getting Started . For a more in-depth overview, see the Tour of Heroes tutorial.

Prerequisites

To complete this tutorial, you should have a basic understanding of the following concepts:

  • Angular CLI

You might find the Tour of Heroes tutorial helpful, but it is not required.

The sample application in action

The sample application for this tutorial helps the Hero Employment Agency find crises for heroes to solve.

The application has three main feature areas:

  • A Crisis Center for maintaining the list of crises for assignment to heroes.
  • A Heroes area for maintaining the list of heroes employed by the agency.
  • An Admin area to manage the list of crises and heroes.

Try it by clicking on this live example link .

The app renders with a row of navigation buttons and the Heroes view with its list of heroes.

[[../File:data:image/png;base64,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|250x225px|Hero List]]

Select one hero and the app takes you to a hero editing screen.

[[../File:data:image/png;base64,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|250x162px|Crisis Center Detail]]

Alter the name. Click the "Back" button and the app returns to the heroes list which displays the changed hero name. Notice that the name change took effect immediately.

Had you clicked the browser's back button instead of the app's "Back" button, the app would have returned you to the heroes list as well. Angular app navigation updates the browser history as normal web navigation does.

Now click the Crisis Center link for a list of ongoing crises.

[[../File:data:image/png;base64,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|250x209px|Crisis Center List]]

Select a crisis and the application takes you to a crisis editing screen. The Crisis Detail appears in a child component on the same page, beneath the list.

Alter the name of a crisis. Notice that the corresponding name in the crisis list does not change.

[[../File:data:image/png;base64,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|250x281px|Crisis Center Detail]]

Unlike Hero Detail , which updates as you type, Crisis Detail changes are temporary until you either save or discard them by pressing the "Save" or "Cancel" buttons. Both buttons navigate back to the Crisis Center and its list of crises.

Click the browser back button or the "Heroes" link to activate a dialog.

[[../File:data:image/png;base64,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|250x101px|Confirm Dialog]]

You can say "OK" and lose your changes or click "Cancel" and continue editing.

Behind this behavior is the router's CanDeactivate guard. The guard gives you a chance to clean-up or ask the user's permission before navigating away from the current view.

The Admin and Login buttons illustrate other router capabilities covered later in the guide.

Milestone 1: Getting started

Begin with a basic version of the app that navigates between two empty views.

[[../File:data:image/gif;base64,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|250x152px|App in action]]

Generate a sample application with the Angular CLI.

Define Routes

A router must be configured with a list of route definitions.

Each definition translates to a Route object which has two things: a path , the URL path segment for this route; and a component , the component associated with this route.

The router draws upon its registry of definitions when the browser URL changes or when application code tells the router to navigate along a route path.

The first route does the following:

  • When the browser's location URL changes to match the path segment /crisis-center , then the router activates an instance of the CrisisListComponent and displays its view.
  • When the application requests navigation to the path /crisis-center , the router activates an instance of CrisisListComponent , displays its view, and updates the browser's address location and history with the URL for that path.

The first configuration defines an array of two routes with minimal paths leading to the CrisisListComponent and HeroListComponent .

Generate the CrisisList and HeroList components so that the router has something to render.

Replace the contents of each component with the sample HTML below.

Register Router and Routes

In order to use the Router , you must first register the RouterModule from the @angular/router package. Define an array of routes, appRoutes , and pass them to the RouterModule.forRoot() method. The RouterModule.forRoot() method returns a module that contains the configured Router service provider, plus other providers that the routing library requires. Once the application is bootstrapped, the Router performs the initial navigation based on the current browser URL.

Note: The RouterModule.forRoot() method is a pattern used to register application-wide providers. Read more about application-wide providers in the Singleton services guide.
Adding the configured RouterModule to the AppModule is sufficient for minimal route configurations. However, as the application grows, refactor the routing configuration into a separate file and create a Routing Module . A routing module is a special type of Service Module dedicated to routing.

Registering the RouterModule.forRoot() in the AppModule imports array makes the Router service available everywhere in the application.

Add the Router Outlet

The root AppComponent is the application shell. It has a title, a navigation bar with two links, and a router outlet where the router renders components.

[[../File:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAABmCAMAAABGF1z5AAAACXBIWXMAAAsTAAALEwEAmpwYAAADAFBMVEX///9Dc8h/oNnsYlzx8fHt8vv5+fnu7u7//v7t7e3pTUbpSEGuw+fsZV85bMX///26yeP39/f9/Pzy8O7o6enw7+/+/f7w7+78+/z95+bx8PD29vbr6+vnPDTwg3/+8vKoqKiVlZVra2voQjuBgYGioqLn5+dXV1fudG/s7O3CwsLZ2dn09PSSkpKcnJyxsbH4+Pjz8vLo7vi/v7/F1O7U1NSlpaVbW1tAccfh4eFHR0dNTU1QUFCqJCx0dHSYmJj6+/urq6vS0tI4a8ScBg2Gpdvl5ubz9vzKysrHx8e1tbXNzc3e3t7X19e7u7twcHBKSkqMjIx4mtifn5/j5OStKTCurq7U5e29WWG4uLjO4+1GdshhYWFoaGjf6e5mv+n28e+wMjjs7u56enrsyMpVgM3m7O6Up7C63O3Q0NCkFx6ks7vb29tDQ0P9/v//8Pjq7e6Omda7Ulyv2ey2Nz7i6+65R02InaeGhob/+Pra5+5ZhM/FxcWj1Owtq+dfvOmzwMVXuend4OL4+v6ApeCXAAPov8OhEBfF4O1uweqnHiaY0Ovw9/29zerh6vnGZWvYmZ8nqOfjt7qRkZGOjo789/f26epNesvSjpSDyep3j5tMtehUVFTHXWD/3NyOq9/9+P95xerAUVXcpaaxtLbP3PJTuOnVf4HisLSQkJCju+Tq3N715OX68fPWjYvId333vsD37O17n92NzOo8r+h+laDP1ti7xcqbrLTakJBymNmsLDk3ruf/6+ynveW0P0jOhIldhtD03t7QcXRljtWCmKONoauyO1Ly1tVwkdNiiM/6xsk9PT1Gs+i5zvrV2t3Cy9CWteXU4PTN2fDE0ezD1vv46/8bpOerucBBsejm7/z///7Fb3a0y+7ku+P03v+qwObFzdHHz9P80tTw6Ojys7bYnMD15Pzo3fKxxuv+/u7QiMhFdMju0+fZ3eDK0dXa5fvNgLjx8/vCZIfQxeepwvCswOOww+XszvnrV1GLldTKd5LdqdKttOHLfaXcsLkab2wgAAAWA0lEQVR42uyabUhbWRrHr93oNduh3Zpcrxuzu9TJpuus3THWWe0o6rYY265dW6dM4NbeK5G8lGzblEuIJpvExH5IiArBXUKSQjYfDFTYLya+IFIQVFDq+FrUFqH2y06htKVMW3ZgYJ9zk1i3Y+2wMagz+X/IOee555xwfj7Pc885EcPSSivVOpwiJTf/XiKUV5pQ1y9SpK6lZOaPj+a01LG7sF6Wl5+O61CqlNz8pzdUfqhkd2HVXR7/aL9oKmPXYTXum6RaW56GtQ9hyXgpkygPXiSiZEbvNVgiPGXi1wAsfjKjf7KwyB8VLBJXq/XxJSnVyrhVr9+8ApLUG9Tk99dNfhiWuJD7FCcFq6MkkyuzX2Ze32Z12S9LOGWmCpZAYQmFIlSsYfIpYutXWtybmei1kVCIVb+7ML1J/SFY4vZBZBpaFycDa0k13onKG9/2d26zuhs3yzmZ79elBJaACkcZ1+qMliRJgYCyOVFB4kqfDxgJBHFWkdFwyGb1qRM21JnEm7UuClqoP04mOr8DSzrQLsIJsVEnJHCRiMBxgiCgILg6HjNttN4Lq3TM+ypG49b2sMwHakFT3rWUwNLbo5SgmZqxG2iDSaFWqAV6BaUW4LSJFNCUiQs9krX6lM1EZFlLCvSUQkniJrWBogVK3yirF5CoP8mN3grWBAdrUicUiS45hDxCKJRCwXc4+GDnOZ4Jebio0OEQE9vDUql6fwisW1yUfj3Wn5kCWAKtx02CW2jd6ghjs7E2ivbZw4yTH4noWZs97DbAU7UrahJA4XPiTsYe9tGkK8TY7VpFeNmuUITCdhfVHLHZbNSWsIb+kZ/Pm9SJhe0azfCgbF03rJl9ptNodA6elDPxTq6g1jawHpaag2P9HRuwnvSBHmC5N/oQQazk0cL1BCwu/upqRx6g4EXdFqBi+qjzo76ujqRhWTxaznlIAbPss7AerTvqZkcZ2mZT2G1syApPSVPYHsvypCJsY92rEXXUGrEEwoqQ1U0zAYvFzhjQaHrLMBwYBvl14nbj0ONhjaPdr5ub1UzODRk503MdmCbn1idW9Nt5lvmrb1RrcVg3gl44eJY3vsD+5e1HlN5471//X88yz3dgbXdV6Kw5Xnod6xqbzzh0K3lYbo+TgyVoZgKmZqdV6/b4nFqtmmEUgRnWyZrgmSlg49K+gGQ9IdYSWKVmGGWze1RhmaEoD2NhXVbWBaO3zllGHcivkxs1c49X/ENDE89kg/5BmWzI+Nw4PPe43T/UPtA+OzjI39azvroxNf4iBuu7p/O9vb3BjPmOh8Fx8KC2myOdiTBUNR4ATY10Y9ld3lvQ7fXT/gdYl3lsrbck+TBkPSxiRTuVjJ0mARbtG12OugGWUhv2eFBokQYmoEY53KmILIdttrBLsRrCSXeUAlja5YDNZrc5XXYTiW+dswrFYumk7pnfqAE9bzc6RHP+SwTvubF9gDPNyVcm/MY55faehZlU8zFYJd3IRxamg3XgQk8wbHH61fUErIynGUgAC8vsRiGZicB1wdAdyFmkYZUxwC7KF1Cg5QIsSqvQMlaKYQwsRVmsPhQdbisLOYsadUPQGgxall4NKeOwqGU3baAsJub9sHiECBL8yYEVqdDx2IFgDfpnCd6QcX2iXS50rD+bnXUMaibk23sW1hE0l7YlEvzClNkLsOqmIO5ejy1sJPj+7kzQ4lh/N2fIbBzzNoJnqe7vyNuQtHhcTspnDZFouU7IWaMWhWuUsjEQaxRrjehRHNrByAaiFDVqd2qjrhisUcoyyhoCUa3TDqjD74E1kHgb6gYeO3TGWYBFXDJqZgeNw3LdwLpjZWJ2ZXLw5LBR+gHPwrqn+/+NYHU0qlRmlQrBqpkf7267ufHqSyR4bMlciuUFY936dw4WjltmrNZoxNDsYiAMV50mxmOdYfUul9IS9Vhd3CaVpFxWq9XmxPnagMfKKJRhn5K0BChFADzR5rEGtM0hGL0lrMkhBEuzIpbr/BBsoiGNg8BnNf4BnUMk1Q34jeuEY9jvnxzEP+BZWPY3qvmRW50Pg6ppAMCFIVYy1tc7rcXegfWw17yG3VVNQ5YraRzZSVikkqa5DQKtxPk0HHxgx4XjagPsRWlamTjuGKBOxjqDrxmghx76qmk1RDINJyF1ouu7xx2hMPbJxwmhXEgShVI4/xB8ubQQdlakVCqFHSoulIu335QiWLBwlaq28/B4P1pIH/IsrO7mSC0geQuLe+XlvIFdae041HO7VI07CQuheM+Jb/ODRP2tjUx0JT98kOZGweYdfXIGIlEQm1rbexZ2eGH6abDz8JR5bXExWH4ZwQJvG5vveAtrZGkR9LocsnrQ+2px8c3TDC7B7xysvX9Fs+RFsLC27w5BzuoaO3T6tLd2ehylqpJpb+nbHfy3p7k7/Ix+2KwuTkE948AUbP27vHsWFo5gJTX6e57V/ehFvCwFNyq9e7ev65+LT+DF+LCjdO3tobltqe8uqO8Jwpi98AhqL79+1I3Rj7qTuylNHSteHuSK//uP8YNvSrOzt7Cl5j6rhsdPkUTFaP5iIpnRe+0Hi5ysFKkmF02fm5fM6PSvO+mfwn7csEYy94sO7D6syxn7RZd3G1bHgX2kxkwsrbTSSiuttHZev/nZPtJ/frnLsA4e/Pl+0cGPdxvWx7/L3i/69a92H9a+SRlpWGlYPwlYuXwpP1bLEkqFSHisiQtjl7tZYmnckiXmxa7lZEJh0buzElJxTuz+VRybRigV50EDj1lzCH4Nxo8/gWdZmxrFWK4s3pDtZVhHTvRUXamq/jOqn2ppKECqqv4MWnnVEgIxONfaAJar6D8Jzku+zILir79tKWiQ/Em6acq8s9VVBQ09Z2ugflISm6agoFWM1bdK6rkeoj8c5WddBGNDFXrcUF98PNGt4RRW/Gm8IflSvGdhiS9WNknutFSWfQrLPHGtqef48eMXr1wrOAUO0lApxLDCv1eUtUgKKsvOgU+drew5guVevXKhQNLSdKGqfmNGWfXtMskdye1jf8wH5hW3WyScqvnY1bJ7d/K5b6oqkNdUg7Hh3jH4bL0ku3KtKtat9TyWf+day1H4bknZ5z28PQqrqLXiaL3sCH6m6dgZDPvk8+qi4uLiIvkX13pqsJqqY2Is71zFxXpcJPxbWdPvMezMsaNHsCLJ7XNCHn7piwutxfFpao5euHied0R0qqHiLznYZ5UScew/7ItysKtN9ypOoNgVtzTIsSIeT/bJhTs8GU+WJ7vSdD7WTVaD5R+vPJEF3/1fds4lNJEtjeNnEYxZNRZaUx0DoxIj0fKFaOErahBJYiwVHygojBM3Eb0LCZIGt1FwYzAxuEiaIVkkTZtHL0NoEpJFB27Wd5OQu+ie7qGHXjS9uXAv03NOPYx59HCZOwvN9KFJn5yc81Xq5/f9v69OHWI0Bx2mHoVF2xdZ7VHbJ6QQlo6VpFlqQszCkr6g5hlh89jjHCyfas7NeJOCEnNmph0TrKr519MSYLYvujvXmk8pVCkrD4sZCblYb0ynfJ1pEFaZ7SULut6EJZhbn+JczAPlBsJiJd1KBcdYWCKXg2YAivLjHCwxFRxnZo1Zec8Kh2i2Y9CrDXdgUfKIPWzoguXvU1gjVOqvfE4ETBgKCNhIS2FFwMKCTkO5/EMGFiIDSyS3BfV5sut1FalYj3aZMdtHB42okQIEyzUyZ6cfhqVhphlFDCw1urTbN9GrYWh1TAi6xtWhoNJikSnn7EEoUCwsN50OhdbnLBoDDwsMyVUheypMi3lcI6lUd8Y3O2wqplFxxrMEAQoG4n1YioKDnRcWAKk8tCiD13alHVpjz8Jyd8Oy2ylKZbuglMhRWFgwH9LaIGVzRIw8LIAFZItpRyFdxngHTd+GpeKynImBJQKyUNg9dB+WnUuaMgLCKqxTlOOiEKTJHi0dxKrOXQqYMAxbowELNZcHXbCQYGniipAM52ExJeiU3EFxtYMx3ZkpwFEYLsICAzaM4GCRMBAH78NKRZlpOMaEod5nnVpU6aS9WpSKJlR5bkwv+xMSeKYbmrDysCRlDkhAFSQZWLimzBVCSruH7WCjDj9nJqkU3xN4EVqdnhr9HQI/PGqLGHoUFu4JRdjerIoa47OhYaWwSnKwAqpFVtXItGKAhaVcT/JRq+xUIKusfo2kHXkES3AHFuEJzSkm7sGK3suGsykH3asV/FhKRSM+vsWCkuiUDta0PcnBgpnMg0SEiDtWCTYM/XZU38O1o/Zpzsxw0B5HDjG0UtAKECxwBxaQjBZs92GN3IOF0zaFtUdh4VOUfdST1CkK4UHQgYXB33iWhYX54WONS+YadSgCnMALIjZqNGLRKmzyjr6Y0/Y5Ga0MFuZ8SODTFhnT4lYeFpikLu7CUqzr2Gky/02dRYYLLkGPPhvigTBlD9nTHrTxoC642HrKuHohlxLB9RGU+eAEmy0lz6Nnw3UtvHOSnli3FexBfVcKnNWmHKFQSjnGZMOCjWmFdRrMczmB8FzwmmWTc551YeOaHD4p2NWsJQ11q9DqrS0aw1hgUjPOQDIG+NJpcHJWAKwaRqcNYo054GM+bTLvQxN/MEYD5jy39cKnwRHNpEbsZnOnmWuBISBll8BA08yy0k0G2O0KzMpPM8OsKA5w6DHf5MjDsHCu/fc3LvzmciH+uDb/npdyTKt++XD5jclPTnf+gymsjSwsVJp3hs8bABwevbw1dmOoT2Et1DJMyxVPvzH5R2fr25aae1VkIFZNtG/B/rtzC4C92C1YB87lfoe1z/z/bLv6/m8Px9Lb2LdhYYe5N4hANlFN7HYvOngA1o+PABYbQOeZzwjW053Gzid0t9kK/BbLZp+dHjrrlVcISKOBxsCrysdso/ITs+pt5vgDa+gs9xuaDic8y2ax0+3afrbJwnrdYE2eHtauKruPwbOwvRz0rMudjaWlpTdfCYBtHv0M72nz+NWhN+etVcDpcm5pyfsvGKuVhT3n0hHLaLn2no0+4XmmTuyW3kCGrxeKu9tLuaWj80MEq1JnTDYBGnNm+xvW8QlqVzkkxtla7dfWXia3RQg33iBYG6W187NacbndPPPut1onufpPIHtc+9JqsNpfz/yDF6+NBdHuZhHBKiWa2XotsfUXBOt5zNlqtUq1azwLDW2t9TcsL/zgl7zeRLYJ7935Ho5VMqVXoAMLvI1Bqcku/Ap/crl1VAHZTOkDnyg3jvmy8PIqI72BBTqatX10jUK39GX3MWhWolGpnHmvkJh8XEgwldF27F0XLCYbbjnfJIrFxCa832zsM3EfFji7BYvPhk82nXBdMRE7fvVIsqHw6ZmzwcCqo3dzwj0YNHdgHea8TKueQFj/5PPm5VmGf2v0unRseADWx4Uqu9D57lHAQtkwmznKdmBBz3p537O+rjFttxsW2HG2uCq9EtvHH/KsUukduxB7NLCebFUTa+DJZuYX5CSbCzAMj35BDDlYlw0nU1lgTawblvDpsfOaYDqbtWuwu4kIN2JdsEAd2YH63xQ+Gljgz/VqQwiWc8ef2qcbXjh2mLtqn9arRQTrZO3Z6w3v5zYccH695VlgJ1a9arfbW87qSRM0t6sn7fPNKoJVa61hCNZB7ui63d6JwZr1ABoi+hiW8HnmCsG6fNJwHn1CAQjlpZZ4B8vNBShQiQUmG3qdFdAoIfGBTibMOrtgXe6UkJpVMyfIzEEGduuZIsqGS5nzvdpLgDEmq8fQ5I8xb6zSz5719GyZ/bCfLZeuofwcwIz3ni3OE8WT54cnu0C4U6zDe3x9lkgkvqJav/7brd2Ew0SiuN/mnmgSxdbT7VYTmitene/U3wHGZHEfPQJgW8V6XxelNy5yCR7aUBHCf7//z8H+8NCf4Lp8NFs0vbD59x3Wd1jfYf2/wcIFAne/wnKP+cvl+cH/LQ+p9OZdBiEV3fqh1CRPYv0Ji6QjJn/yxUzXAQN8QPQHzZN65c37mSFL/PZJj0F5vD9hEbRcA0sOmu6CRUam/6B5d3nmxlXJW8ZhM0b6FFZUi47VAglzhpsYkECfwvNaWoI6QDQwMCDApcPDJJxjlBjhV8w4bAA4CUdw47AUuI2804gkEinuHhh2A9LolkpIhEMqkZAERjLL4TfDJPcCVxnHBQMGpo8xl8SMAyRwD4jgPJK5TG/CUq9OdgYl02oTHcUHk2ElbULHDXwzMvUwrol7Apho3qSmp4zA7ffMY8SkR+0mAvGy1SRjX7ljs2p1csZqVMvyAb0nOhunjYDIq9W0Pk+YPdMG4Naoy0m9lYcVpS1J5HvGKZPJZCWI+bjaR1vmcSIAL+Pv0fNZePxF9EZoPFZrXDkyPLkyY7WiTUVRcrGMA7FSNkiodfNjfnnSAGa1tBv4XDIDGNfpaNrFnjkKRKZ98ytq4A/HTfpwwCiLDIGoqzymUdJArIOaZdWVxWbdNAdLZzJ5wmo3kCZls9F4RAxG5Dr1jJbGJyN+eJky3pOwMMsLMeCesyZX8gCYw/NgXMudhAVilLdG4mYQldNQiZLaWTDugrBIGYRlkMnzmJU53SW1yATQeaJAo9UbBeZBwYxyEPhXzeiILjBa9P9u52xWHYWhOJ6FWDrDLG6wQW67iAwZqFXTIga6Sr0LsVoFiQhuZyfcR/Atri9xH3F2k/hRpsMMzK52uFkUGpKov/zPyUl6rAPiLpM2b0ywjmApsAMyS9ZmXYp0jx/NS/RMEgieGHHmCQu/R1JBKZXmkhQ0z6kVo80VFqz4AaXyWdNCvXHRFDHYKFgHBeuJ4WnVPFr+4LIzt89p2ytYhsdtldPQw4o4b6cFUpqhCXRMHOQPl2zNvYfVjF0skue1YNt5+izavSqPw/k3GFq7soyiAzCusEDQBQcqn98uMoWiON/ACqdVLntrJ1gvaIKFysTi7X6ABY/U5fF+ggWBGTLnRK0gkpfU0JMXDuL2S3kPG32esOLC7idf2gS1xqk3XPvqx7xqh1cAnIsAAdR0zQiL3cKSjswcYTVXWADoEX2PB1iyrgzfgltYZm2NHnOEtXNbNOPQYWMpB7EgoXIrLUJOE0lY1FwPP4SYvhuee/uoT+CU8wgsPLyGFze8gbUS3gLpu9cbWK/SwLdurWCt+y+XUbFXWNKubYT28bcJ1tYjGkJlas40KI3dxFjFroS1Im7ih7UG1uStTpoxDuuE0tsp4ZmWeTYEelUkfiLICn1XH+N4bcf8pNpK7fVvhWkhj1DNSi3gUouCrUCCDS31dv0KbFh4DZ6JtwAac6kdJhpYctL7P/ud+TXO5rrdgRllOLfPUiSbWpDWUVEpJum4112TIQnPaXFVvajKiAp/acsGL4xNrYCeMiEDp0PuVSUCcMdIrMv4THYxzZSQ4JRVsn/WK+Z0FjiDASNSQMucEFsDMBa4UWPpDRbVZdanDioy/0tDA0/J178lzCHVy/zDHhoOA5qoT9QbZwT92h+qCnjTGw5tHvmIZlNG1EfgzuVBYKVc0OePw79/VFbT3J/Vx7HyB6z/FtbnT18epfz4ev8/7nmccmdYPwFdPG1LBQ+/kQAAAABJRU5ErkJggg==|300x102px|Shell]]

The router outlet serves as a placeholder where the routed components are rendered.

The corresponding component template looks like this:

Define a Wildcard route

You've created two routes in the app so far, one to /crisis-center and the other to /heroes . Any other URL causes the router to throw an error and crash the app.

Add a wildcard route to intercept invalid URLs and handle them gracefully. A wildcard route has a path consisting of two asterisks. It matches every URL. Thus, the router selects this wildcard route if it can't match a route earlier in the configuration. A wildcard route can navigate to a custom "404 Not Found" component or redirect to an existing route.

The router selects the route with a first match wins strategy. Because a wildcard route is the least specific route, place it last in the route configuration.

To test this feature, add a button with a RouterLink to the HeroListComponent template and set the link to a non-existant route called "/sidekicks" .

The application fails if the user clicks that button because you haven't defined a "/sidekicks" route yet.

Instead of adding the "/sidekicks" route, define a wildcard route and have it navigate to a PageNotFoundComponent .

Create the PageNotFoundComponent to display when users visit invalid URLs.

Now when the user visits /sidekicks , or any other invalid URL, the browser displays "Page not found". The browser address bar continues to point to the invalid URL.

Set up redirects

When the application launches, the initial URL in the browser bar is by default:

That doesn't match any of the hard-coded routes which means the router falls through to the wildcard route and displays the PageNotFoundComponent .

The application needs a default route to a valid page. The default page for this app is the list of heroes. The app should navigate there as if the user clicked the "Heroes" link or pasted localhost:4200/heroes into the address bar.

Add a redirect route that translates the initial relative URL ( ) to the desired default path ( /heroes ).

Add the default route somewhere above the wildcard route. It's just above the wildcard route in the following excerpt showing the complete appRoutes for this milestone.

The browser address bar shows .../heroes as if you'd navigated there directly.

A redirect route requires a pathMatch property to tell the router how to match a URL to the path of a route. In this app, the router should select the route to the HeroListComponent only when the entire URL matches , so set the pathMatch value to 'full' .

Spotlight on pathMatch

Technically, pathMatch = 'full' results in a route hit when the remaining , unmatched segments of the URL match . In this example, the redirect is in a top level route so the remaining URL and the entire URL are the same thing.

The other possible pathMatch value is 'prefix' which tells the router to match the redirect route when the remaining URL begins with the redirect route's prefix path. This doesn't apply to this sample app because if the pathMatch value were 'prefix' , every URL would match .

Try setting it to 'prefix' and clicking the Go to sidekicks button. Since that's a bad URL, you should see the "Page not found" page. Instead, you're still on the "Heroes" page. Enter a bad URL in the browser address bar. You're instantly re-routed to /heroes . Every URL, good or bad, that falls through to this route definition is a match.

The default route should redirect to the HeroListComponent only when the entire url is . Remember to restore the redirect to pathMatch = 'full' .

Learn more in Victor Savkin's post on redirects .

Milestone 1 wrap up

Your sample app can switch between two views when the user clicks a link.

Milestone 1 has covered how to do the following:

  • Load the router library.
  • Add a nav bar to the shell template with anchor tags, routerLink and routerLinkActive directives.
  • Add a router-outlet to the shell template where views are displayed.
  • Configure the router module with RouterModule.forRoot() .
  • Set the router to compose HTML5 browser URLs.
  • Handle invalid routes with a wildcard route.
  • Navigate to the default route when the app launches with an empty path.

The starter app's structure looks like this:

Here are the files in this milestone.

Milestone 2: Routing module

This milestone shows you how to configure a special-purpose module called a Routing Module , which holds your app's routing configuration.

The Routing Module has several characteristics:

  • Separates routing concerns from other application concerns.
  • Provides a module to replace or remove when testing the application.
  • Provides a well-known location for routing service providers such as guards and resolvers.
  • Does not declare components.

Integrate routing with your app

The sample routing application does not include routing by default. When you use the Angular CLI to create a project that does use routing, set the --routing option for the project or app, and for each NgModule. When you create or initialize a new project (using the CLI ng new command) or a new app (using the ng generate app command), specify the --routing option. This tells the CLI to include the @angular/router npm package and create a file named app-routing.module.ts . You can then use routing in any NgModule that you add to the project or app.

For example, the following command generates an NgModule that can use routing.

This creates a separate file named my-module-routing.module.ts to store the NgModule's routes. The file includes an empty Routes object that you can fill with routes to different components and NgModules.

Refactor the routing configuration into a routing module

Create an AppRouting module in the /app folder to contain the routing configuration.

Import the CrisisListComponent , HeroListComponent , and PageNotFoundComponent symbols just like you did in the app.module.ts . Then move the Router imports and routing configuration, including RouterModule.forRoot() , into this routing module.

Re-export the Angular RouterModule by adding it to the module exports array. By re-exporting the RouterModule here, the components declared in AppModule have access to router directives such as RouterLink and RouterOutlet .

After these steps, the file should look like this.

Next, update the app.module.ts file by removing RouterModule.forRoot in the imports array.

Later, this guide shows you how to create multiple routing modules and import those routing modules in the correct order .

The application continues to work just the same, and you can use AppRoutingModule as the central place to maintain future routing configuration.

Benefits of a routing module

The routing module, often called the AppRoutingModule , replaces the routing configuration in the root or feature module.

The routing module is helpful as your app grows and when the configuration includes specialized guard and resolver services.

Some developers skip the routing module when the configuration is minimal and merge the routing configuration directly into the companion module (for example, AppModule ).

Most apps should implement a routing module for consistency. It keeps the code clean when configuration becomes complex. It makes testing the feature module easier. Its existence calls attention to the fact that a module is routed. It is where developers expect to find and expand routing configuration.

Milestone 3: Heroes feature

This milestone covers the following:

  • Organizing the app and routes into feature areas using modules.
  • Navigating imperatively from one component to another.
  • Passing required and optional information in route parameters.

This sample app recreates the heroes feature in the "Services" section of the Tour of Heroes tutorial , and reuses much of the code from the .

A typical application has multiple feature areas, each dedicated to a particular business purpose with its own folder.

This section shows you how refactor the app into different feature modules, import them into the main module and navigate among them.

Add heroes functionality

Follow these steps:

  • To manage the heroes, create a HeroesModule with routing in the heroes folder and register it with the root AppModule .
  • Move the placeholder hero-list folder that's in the app folder into the heroes folder.
  • Re-label the <h2> to <h2>HEROES</h2> .
  • Delete the <app-hero-detail> component at the bottom of the template.
  • Copy the contents of the heroes/heroes.component.css from the live example into the hero-list.component.css file.
  • Change the component class name to HeroListComponent .
  • Change the selector to app-hero-list .
Selectors are not required for routed components because components are dynamically inserted when the page is rendered. However, they are useful for identifying and targeting them in your HTML element tree.
  • Copy the hero-detail folder, the hero.ts , hero.service.ts , and mock-heroes.ts files into the heroes subfolder.
  • Copy the message.service.ts into the src/app folder.
  • Update the relative path import to the message.service in the hero.service.ts file.

Next, update the HeroesModule metadata.

  • Import and add the HeroDetailComponent and HeroListComponent to the declarations array in the HeroesModule .

The hero management file structure is as follows:

Hero feature routing requirements

The heroes feature has two interacting components, the hero list and the hero detail. When you navigate to list view, it gets a list of heroes and displays them. When you click on a hero, the detail view has to display that particular hero.

You tell the detail view which hero to display by including the selected hero's id in the route URL.

Import the hero components from their new locations in the src/app/heroes/ folder and define the two hero routes.

Now that you have routes for the Heroes module, register them with the Router via the RouterModule as you did in the AppRoutingModule , with an important difference.

In the AppRoutingModule , you used the static RouterModule.forRoot() method to register the routes and application level service providers. In a feature module you use the static forChild() method.

Only call RouterModule.forRoot() in the root AppRoutingModule (or the AppModule if that's where you register top level application routes). In any other module, you must call the RouterModule.forChild() method to register additional routes.

The updated HeroesRoutingModule looks like this:

Consider giving each feature module its own route configuration file. Though the feature routes are currently minimal, routes have a tendency to grow more complex even in small apps.

Remove duplicate hero routes

The hero routes are currently defined in two places: in the HeroesRoutingModule , by way of the HeroesModule , and in the AppRoutingModule .

Routes provided by feature modules are combined together into their imported module's routes by the router. This allows you to continue defining the feature module routes without modifying the main route configuration.

Remove the HeroListComponent import and the /heroes route from the app-routing.module.ts .

Leave the default and the wildcard routes as these are still in use at the top level of the application.

Remove heroes declarations

Because the HeroesModule now provides the HeroListComponent , remove it from the AppModule 's declarations array. Now that you have a separate HeroesModule , you can evolve the hero feature with more components and different routes.

After these steps, the AppModule should look like this:

Module import order

Notice that in the module imports array, the AppRoutingModule is last and comes after the HeroesModule .

The order of route configuration is important because the router accepts the first route that matches a navigation request path.

When all routes were in one AppRoutingModule , you put the default and wildcard routes last, after the /heroes route, so that the router had a chance to match a URL to the /heroes route before hitting the wildcard route and navigating to "Page not found".

Each routing module augments the route configuration in the order of import. If you listed AppRoutingModule first, the wildcard route would be registered before the hero routes. The wildcard route—which matches every URL—would intercept the attempt to navigate to a hero route.

Reverse the routing modules to see a click of the heroes link resulting in "Page not found". Learn about inspecting the runtime router configuration below .

Route Parameters

Route definition with a parameter.

Return to the HeroesRoutingModule and look at the route definitions again. The route to HeroDetailComponent has an :id token in the path.

The :id token creates a slot in the path for a Route Parameter. In this case, this configuration causes the router to insert the id of a hero into that slot.

If you tell the router to navigate to the detail component and display "Magneta", you expect a hero id to appear in the browser URL like this:

If a user enters that URL into the browser address bar, the router should recognize the pattern and go to the same "Magneta" detail view.

Route parameter: Required or optional?

Embedding the route parameter token, :id , in the route definition path is a good choice for this scenario because the id is required by the HeroDetailComponent and because the value 15 in the path clearly distinguishes the route to "Magneta" from a route for some other hero.

Setting the route parameters in the list view

After navigating to the HeroDetailComponent , you expect to see the details of the selected hero. You need two pieces of information: the routing path to the component and the hero's id .

Accordingly, the link parameters array has two items: the routing path and a route parameter that specifies the id of the selected hero.

The router composes the destination URL from the array like this: localhost:4200/hero/15 .

The router extracts the route parameter ( id:15 ) from the URL and supplies it to the HeroDetailComponent via the ActivatedRoute service.

Activated Route in action

Import the Router , ActivatedRoute , and ParamMap tokens from the router package.

Import the switchMap operator because you need it later to process the Observable route parameters.

Add the services as private variables to the constructor so that Angular injects them (makes them visible to the component).

In the ngOnInit() method, use the ActivatedRoute service to retrieve the parameters for the route, pull the hero id from the parameters, and retrieve the hero to display.

When the map changes, paramMap gets the id parameter from the changed parameters.

Then you tell the HeroService to fetch the hero with that id and return the result of the HeroService request.

The switchMap operator does two things. It flattens the Observable<Hero> that HeroService returns and cancels previous pending requests. If the user re-navigates to this route with a new id while the HeroService is still retrieving the old id , switchMap discards that old request and returns the hero for the new id .

AsyncPipe handles the observable subscription and the component's hero property will be (re)set with the retrieved hero.

ParamMap API

The ParamMap API is inspired by the URLSearchParams interface . It provides methods to handle parameter access for both route parameters ( paramMap ) and query parameters ( queryParamMap ).

Observable paramMap and component reuse

In this example, you retrieve the route parameter map from an Observable . That implies that the route parameter map can change during the lifetime of this component.

By default, the router re-uses a component instance when it re-navigates to the same component type without visiting a different component first. The route parameters could change each time.

Suppose a parent component navigation bar had "forward" and "back" buttons that scrolled through the list of heroes. Each click navigated imperatively to the HeroDetailComponent with the next or previous id .

You wouldn't want the router to remove the current HeroDetailComponent instance from the DOM only to re-create it for the next id as this would re-render the view. For better UX, the router re-uses the same component instance and updates the parameter.

Since ngOnInit() is only called once per component instantiation, you can detect when the route parameters change from within the same instance using the observable paramMap property.

When subscribing to an observable in a component, you almost always unsubscribe when the component is destroyed. However, ActivatedRoute observables are among the exceptions because ActivatedRoute and its observables are insulated from the Router itself. The Router destroys a routed component when it is no longer needed along with the injected ActivatedRoute .

snapshot: the no-observable alternative

This application won't re-use the HeroDetailComponent . The user always returns to the hero list to select another hero to view. There's no way to navigate from one hero detail to another hero detail without visiting the list component in between. Therefore, the router creates a new HeroDetailComponent instance every time.

When you know for certain that a HeroDetailComponent instance will never be re-used, you can use snapshot .

route.snapshot provides the initial value of the route parameter map. You can access the parameters directly without subscribing or adding observable operators as in the following:

snapshot only gets the initial value of the parameter map with this technique. Use the observable paramMap approach if there's a possibility that the router could re-use the component. This tutorial sample app uses with the observable paramMap .

Navigating back to the list component

The HeroDetailComponent "Back" button uses the gotoHeroes() method that navigates imperatively back to the HeroListComponent .

The router navigate() method takes the same one-item link parameters array that you can bind to a [routerLink] directive. It holds the path to the HeroListComponent :

Route Parameters: Required or optional?

Use route parameters to specify a required parameter value within the route URL as you do when navigating to the HeroDetailComponent in order to view the hero with id 15:

You can also add optional information to a route request. For example, when returning to the hero-detail.component.ts list from the hero detail view, it would be nice if the viewed hero were preselected in the list.

[[../File:data:image/png;base64,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|336x110px|Selected hero]]

You implement this feature by including the viewed hero's id in the URL as an optional parameter when returning from the HeroDetailComponent .

Optional information can also include other forms such as:

  • Loosely structured search criteria; for example, name='wind*' .
  • Multiple values; for example, after='12/31/2015' & before='1/1/2017' —in no particular order— before='1/1/2017' & after='12/31/2015' — in a variety of formats— during='currentYear' .

As these kinds of parameters don't fit easily in a URL path, you can use optional parameters for conveying arbitrarily complex information during navigation. Optional parameters aren't involved in pattern matching and afford flexibility of expression.

The router supports navigation with optional parameters as well as required route parameters. Define optional parameters in a separate object after you define the required route parameters.

In general, use a required route parameter when the value is mandatory (for example, if necessary to distinguish one route path from another); and an optional parameter when the value is optional, complex, and/or multivariate.

Heroes list: optionally selecting a hero

When navigating to the HeroDetailComponent you specified the required id of the hero-to-edit in the route parameter and made it the second item of the link parameters array .

The router embedded the id value in the navigation URL because you had defined it as a route parameter with an :id placeholder token in the route path :

When the user clicks the back button, the HeroDetailComponent constructs another link parameters array which it uses to navigate back to the HeroListComponent .

This array lacks a route parameter because previously you didn't need to send information to the HeroListComponent .

Now, send the id of the current hero with the navigation request so that the HeroListComponent can highlight that hero in its list.

Send the id with an object that contains an optional id parameter. For demonstration purposes, there's an extra junk parameter ( foo ) in the object that the HeroListComponent should ignore. Here's the revised navigation statement:

The application still works. Clicking "back" returns to the hero list view.

Look at the browser address bar.

It should look something like this, depending on where you run it:

The id value appears in the URL as ( ;id=15;foo=foo ), not in the URL path. The path for the "Heroes" route doesn't have an :id token.

The optional route parameters are not separated by "?" and "&" as they would be in the URL query string. They are separated by semicolons ";". This is matrix URL notation.

Matrix URL notation is an idea first introduced in a 1996 proposal by the founder of the web, Tim Berners-Lee. Although matrix notation never made it into the HTML standard, it is legal and it became popular among browser routing systems as a way to isolate parameters belonging to parent and child routes. As such, the Router provides support for the matrix notation across browsers.

Route parameters in the ActivatedRoute service

In its current state of development, the list of heroes is unchanged. No hero row is highlighted.

The HeroListComponent needs code that expects parameters.

Previously, when navigating from the HeroListComponent to the HeroDetailComponent , you subscribed to the route parameter map Observable and made it available to the HeroDetailComponent in the ActivatedRoute service. You injected that service in the constructor of the HeroDetailComponent .

This time you'll be navigating in the opposite direction, from the HeroDetailComponent to the HeroListComponent .

First, extend the router import statement to include the ActivatedRoute service symbol:

Import the switchMap operator to perform an operation on the Observable of route parameter map.

Inject the ActivatedRoute in the HeroListComponent constructor.

The ActivatedRoute.paramMap property is an Observable map of route parameters. The paramMap emits a new map of values that includes id when the user navigates to the component. In ngOnInit() you subscribe to those values, set the selectedId , and get the heroes.

Update the template with a class binding . The binding adds the selected CSS class when the comparison returns true and removes it when false . Look for it within the repeated <li> tag as shown here:

Add some styles to apply when the list item is selected.

When the user navigates from the heroes list to the "Magneta" hero and back, "Magneta" appears selected:

[[../File:data:image/png;base64,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|336x110px|Selected List]]

The optional foo route parameter is harmless and the router continues to ignore it.

Adding routable animations

This section shows you how to add some animations to the HeroDetailComponent .

First, import the BrowserAnimationsModule and add it to the imports array:

Next, add a data object to the routes for HeroListComponent and HeroDetailComponent . Transitions are based on states and you use the animation data from the route to provide a named animation state for the transitions.

Create an animations.ts file in the root src/app/ folder. The contents look like this:

This file does the following:

  • Imports the animation symbols that build the animation triggers, control state, and manage transitions between states.
  • Exports a constant named slideInAnimation set to an animation trigger named routeAnimation .
  • Defines one transition when switching back and forth from the heroes and hero routes to ease the component in from the left of the screen as it enters the application view ( :enter ), the other to animate the component to the right as it leaves the application view ( :leave ).

Back in the AppComponent , import the RouterOutlet token from the @angular/router package and the slideInAnimation from './animations.ts .

Add an animations array to the @Component metadata that contains the slideInAnimation .

In order to use the routable animations, wrap the RouterOutlet inside an element, use the @routeAnimation trigger, and bind it to the element.

For the @routeAnimation transitions to key off states, provide it with the data from the ActivatedRoute . The RouterOutlet is exposed as an outlet template variable, so you bind a reference to the router outlet. This example uses a variable of routerOutlet .

The @routeAnimation property is bound to the getAnimationData() with the provided routerOutlet reference, so the next step is to define that function in the AppComponent . The getAnimationData() function returns the animation property from the data provided through the ActivatedRoute . The animation property matches the transition names you used in the slideInAnimation defined in animations.ts .

When switching between the two routes, the HeroDetailComponent and HeroListComponent now ease in from the left when routed to and will slide to the right when navigating away.

Milestone 3 wrap up

This section has covered the following:

  • Organizing the app into feature areas.
  • Passing information along in route parameters and subscribe to them in the component.
  • Importing the feature area NgModule into the AppModule .
  • Applying routable animations based on the page.

After these changes, the folder structure is as follows:

Here are the relevant files for this version of the sample application.

Milestone 4: Crisis center feature

This section shows you how to add child routes and use relative routing in your app.

To add more features to the app's current crisis center, take similar steps as for the heroes feature:

  • Create a crisis-center subfolder in the src/app folder.
  • Copy the files and folders from app/heroes into the new crisis-center folder.
  • In the new files, change every mention of "hero" to "crisis", and "heroes" to "crises".
  • Rename the NgModule files to crisis-center.module.ts and crisis-center-routing.module.ts .

Use mock crises instead of mock heroes:

The resulting crisis center is a foundation for introducing a new concept—child routing. You can leave Heroes in its current state as a contrast with the Crisis Center.

In keeping with the Separation of Concerns principle , changes to the Crisis Center don't affect the AppModule or any other feature's component.

A crisis center with child routes

This section shows you how to organize the crisis center to conform to the following recommended pattern for Angular applications:

  • Each feature area resides in its own folder.
  • Each feature has its own Angular feature module.
  • Each area has its own area root component.
  • Each area root component has its own router outlet and child routes.
  • Feature area routes rarely (if ever) cross with routes of other features.

If your app had many feature areas, the app component trees might look like this:

[[../File:data:image/png;base64,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|473x258px|Component Tree]]

Child routing component

Generate a CrisisCenter component in the crisis-center folder:

Update the component template with the following markup:

The CrisisCenterComponent has the following in common with the AppComponent :

  • It is the root of the crisis center area, just as AppComponent is the root of the entire application.
  • It is a shell for the crisis management feature area, just as the AppComponent is a shell to manage the high-level workflow.

Like most shells, the CrisisCenterComponent class is minimal because it has no business logic, and its template has no links, just a title and <router-outlet> for the crisis center child component.

Child route configuration

As a host page for the "Crisis Center" feature, generate a CrisisCenterHome component in the crisis-center folder.

Update the template with a welcome message to the Crisis Center .

Update the crisis-center-routing.module.ts you renamed after copying it from heroes-routing.module.ts file. This time, you define child routes within the parent crisis-center route.

Notice that the parent crisis-center route has a children property with a single route containing the CrisisListComponent . The CrisisListComponent route also has a children array with two routes.

These two routes navigate to the crisis center child components, CrisisCenterHomeComponent and CrisisDetailComponent , respectively.

There are important differences in the way the router treats child routes.

The router displays the components of these routes in the RouterOutlet of the CrisisCenterComponent , not in the RouterOutlet of the AppComponent shell.

The CrisisListComponent contains the crisis list and a RouterOutlet to display the Crisis Center Home and Crisis Detail route components.

The Crisis Detail route is a child of the Crisis List . The router reuses components by default, so the Crisis Detail component will be re-used as you select different crises. In contrast, back in the Hero Detail route, the component was recreated each time you selected a different hero from the list of heroes.

At the top level, paths that begin with / refer to the root of the application. But child routes extend the path of the parent route. With each step down the route tree, you add a slash followed by the route path, unless the path is empty.

Apply that logic to navigation within the crisis center for which the parent path is /crisis-center .

  • To navigate to the CrisisCenterHomeComponent , the full URL is /crisis-center ( /crisis-center + + ).
  • To navigate to the CrisisDetailComponent for a crisis with id=2 , the full URL is /crisis-center/2 ( /crisis-center + + '/2' ).

The absolute URL for the latter example, including the localhost origin, is as follows:

Here's the complete crisis-center-routing.module.ts file with its imports.

Import crisis center module into the AppModule routes

As with the HeroesModule , you must add the CrisisCenterModule to the imports array of the AppModule before the AppRoutingModule :

Remove the initial crisis center route from the app-routing.module.ts because now the HeroesModule and the CrisisCenter modules provide the feature routes.

The app-routing.module.ts file retains the top-level application routes such as the default and wildcard routes.

Relative navigation

While building out the crisis center feature, you navigated to the crisis detail route using an absolute path that begins with a slash.

The router matches such absolute paths to routes starting from the top of the route configuration.

You could continue to use absolute paths like this to navigate inside the Crisis Center feature, but that pins the links to the parent routing structure. If you changed the parent /crisis-center path, you would have to change the link parameters array.

You can free the links from this dependency by defining paths that are relative to the current URL segment. Navigation within the feature area remains intact even if you change the parent route path to the feature.

The router supports directory-like syntax in a link parameters list to help guide route name lookup: ./ or no leading slash is relative to the current level. ../ to go up one level in the route path. You can combine relative navigation syntax with an ancestor path. If you must navigate to a sibling route, you could use the ../<sibling> convention to go up one level, then over and down the sibling route path.

To navigate a relative path with the Router.navigate method, you must supply the ActivatedRoute to give the router knowledge of where you are in the current route tree.

After the link parameters array , add an object with a relativeTo property set to the ActivatedRoute . The router then calculates the target URL based on the active route's location.

Always specify the complete absolute path when calling router's navigateByUrl() method.

Navigate to crisis list with a relative URL

You've already injected the ActivatedRoute that you need to compose the relative navigation path.

When using a RouterLink to navigate instead of the Router service, you'd use the same link parameters array, but you wouldn't provide the object with the relativeTo property. The ActivatedRoute is implicit in a RouterLink directive.

Update the gotoCrises() method of the CrisisDetailComponent to navigate back to the Crisis Center list using relative path navigation.

Notice that the path goes up a level using the ../ syntax. If the current crisis id is 3 , the resulting path back to the crisis list is /crisis-center/;id=3;foo=foo .

Displaying multiple routes in named outlets

You decide to give users a way to contact the crisis center. When a user clicks a "Contact" button, you want to display a message in a popup view.

The popup should stay open, even when switching between pages in the application, until the user closes it by sending the message or canceling. Clearly you can't put the popup in the same outlet as the other pages.

Until now, you've defined a single outlet and you've nested child routes under that outlet to group routes together. The router only supports one primary unnamed outlet per template.

A template can also have any number of named outlets. Each named outlet has its own set of routes with their own components. Multiple outlets can display different content, determined by different routes, all at the same time.

Add an outlet named "popup" in the AppComponent , directly below the unnamed outlet.

That's where a popup will go, once you learn how to route a popup component to it.

Secondary routes

Named outlets are the targets of secondary routes .

Secondary routes look like primary routes and you configure them the same way. They differ in a few key respects.

  • They are independent of each other.
  • They work in combination with other routes.
  • They are displayed in named outlets.

Generate a new component to compose the message.

It displays a short form with a header, an input box for the message, and two buttons, "Send" and "Cancel".

[[../File:data:image/png;base64,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|250x262px|Contact popup]]

Here's the component, its template and styles:

It looks similar to any other component in this guide, but there are two key differences.

Note that the send() method simulates latency by waiting a second before "sending" the message and closing the popup.

The closePopup() method closes the popup view by navigating to the popup outlet with a null which the section on clearing secondary routes covers.

Add a secondary route

Open the AppRoutingModule and add a new compose route to the appRoutes .

In addition to the path and component properties, there's a new property called outlet , which is set to 'popup' . This route now targets the popup outlet and the ComposeMessageComponent will display there.

To give users a way to open the popup, add a "Contact" link to the AppComponent template.

Although the compose route is configured to the "popup" outlet, that's not sufficient for connecting the route to a RouterLink directive. You have to specify the named outlet in a link parameters array and bind it to the RouterLink with a property binding.

The link parameters array contains an object with a single outlets property whose value is another object keyed by one (or more) outlet names. In this case there is only the "popup" outlet property and its value is another link parameters array that specifies the compose route.

In other words, when the user clicks this link, the router displays the component associated with the compose route in the popup outlet.

This outlets object within an outer object was unnecessary when there was only one route and one unnamed outlet. The router assumed that your route specification targeted the unnamed primary outlet and created these objects for you. Routing to a named outlet has revealed a router feature: you can target multiple outlets with multiple routes in the same RouterLink directive.

Secondary route navigation: merging routes during navigation

Navigate to the Crisis Center and click "Contact". you should see something like the following URL in the browser address bar.

The relevant part of the URL follows the ... :

  • The crisis-center is the primary navigation.
  • Parentheses surround the secondary route.
  • The secondary route consists of an outlet name ( popup ), a colon separator, and the secondary route path ( compose ).

Click the Heroes link and look at the URL again.

The primary navigation part has changed; the secondary route is the same.

The router is keeping track of two separate branches in a navigation tree and generating a representation of that tree in the URL.

You can add many more outlets and routes, at the top level and in nested levels, creating a navigation tree with many branches and the router will generate the URLs to go with it.

You can tell the router to navigate an entire tree at once by filling out the outlets object and then pass that object inside a link parameters array to the router.navigate method.

Clearing secondary routes

Like regular outlets, secondary outlets persists until you navigate away to a new component.

Each secondary outlet has its own navigation, independent of the navigation driving the primary outlet. Changing a current route that displays in the primary outlet has no effect on the popup outlet. That's why the popup stays visible as you navigate among the crises and heroes.

The closePopup() method again:

Clicking the "send" or "cancel" buttons clears the popup view. The closePopup() function navigates imperatively with the Router.navigate() method, passing in a link parameters array .

Like the array bound to the Contact RouterLink in the AppComponent , this one includes an object with an outlets property. The outlets property value is another object with outlet names for keys. The only named outlet is 'popup' .

This time, the value of 'popup' is null . That's not a route, but it is a legitimate value. Setting the popup RouterOutlet to null clears the outlet and removes the secondary popup route from the current URL.

Milestone 5: Route guards

At the moment, any user can navigate anywhere in the application anytime, but sometimes you need to control access to different parts of your app for various reasons. Some of which may include the following:

  • Perhaps the user is not authorized to navigate to the target component.
  • Maybe the user must login (authenticate) first.
  • Maybe you should fetch some data before you display the target component.
  • You might want to save pending changes before leaving a component.
  • You might ask the user if it's OK to discard pending changes rather than save them.

You add guards to the route configuration to handle these scenarios.

A guard's return value controls the router's behavior:

  • If it returns true , the navigation process continues.
  • If it returns false , the navigation process stops and the user stays put.
  • If it returns a UrlTree , the current navigation cancels and a new navigation is initiated to the UrlTree returned.
Note: The guard can also tell the router to navigate elsewhere, effectively canceling the current navigation. When doing so inside a guard, the guard should return false ;

The guard might return its boolean answer synchronously. But in many cases, the guard can't produce an answer synchronously. The guard could ask the user a question, save changes to the server, or fetch fresh data. These are all asynchronous operations.

Accordingly, a routing guard can return an Observable<boolean> or a Promise<boolean> and the router will wait for the observable to resolve to true or false .

Note: The observable provided to the Router must also complete. If the observable does not complete, the navigation does not continue.

The router supports multiple guard interfaces:

  • CanActivate to mediate navigation to a route.
  • CanActivateChild to mediate navigation to a child route.
  • CanDeactivate to mediate navigation away from the current route.
  • Resolve to perform route data retrieval before route activation.
  • CanLoad to mediate navigation to a feature module loaded asynchronously .

You can have multiple guards at every level of a routing hierarchy. The router checks the CanDeactivate and CanActivateChild guards first, from the deepest child route to the top. Then it checks the CanActivate guards from the top down to the deepest child route. If the feature module is loaded asynchronously, the CanLoad guard is checked before the module is loaded. If any guard returns false, pending guards that have not completed will be canceled, and the entire navigation is canceled.

There are several examples over the next few sections.

CanActivate: requiring authentication

Applications often restrict access to a feature area based on who the user is. You could permit access only to authenticated users or to users with a specific role. You might block or limit access until the user's account is activated.

The CanActivate guard is the tool to manage these navigation business rules.

Add an admin feature module

This section guides you through extending the crisis center with some new administrative features. Start by adding a new feature module named AdminModule .

Generate an admin folder with a feature module file and a routing configuration file.

Next, generate the supporting components.

The admin feature file structure looks like this:

The admin feature module contains the AdminComponent used for routing within the feature module, a dashboard route and two unfinished components to manage crises and heroes.

Although the admin dashboard RouterLink only contains a relative slash without an additional URL segment, it is a match to any route within the admin feature area. You only want the Dashboard link to be active when the user visits that route. Adding an additional binding to the Dashboard routerLink, [routerLinkActiveOptions]="{ exact: true }" , marks the ./ link as active when the user navigates to the /admin URL and not when navigating to any of the child routes.

Component-less route: grouping routes without a component

The initial admin routing configuration:

The child route under the AdminComponent has a path and a children property but it's not using a component . This defines a component-less route.

To group the Crisis Center management routes under the admin path a component is unnecessary. Additionally, a component-less route makes it easier to guard child routes .

Next, import the AdminModule into app.module.ts and add it to the imports array to register the admin routes.

Add an "Admin" link to the AppComponent shell so that users can get to this feature.

Guard the admin feature

Currently, every route within the Crisis Center is open to everyone. The new admin feature should be accessible only to authenticated users.

Write a canActivate() guard method to redirect anonymous users to the login page when they try to enter the admin area.

Generate an AuthGuard in the auth folder.

To demonstrate the fundamentals, this example only logs to the console, returns true immediately, and allows navigation to proceed:

Next, open admin-routing.module.ts , import the AuthGuard class, and update the admin route with a canActivate guard property that references it:

The admin feature is now protected by the guard, but the guard requires more customization to work fully.

Authenticate with AuthGuard

Make the AuthGuard mimic authentication.

The AuthGuard should call an application service that can login a user and retain information about the current user. Generate a new AuthService in the auth folder:

Update the AuthService to log in the user:

Although it doesn't actually log in, it has an isLoggedIn flag to tell you whether the user is authenticated. Its login() method simulates an API call to an external service by returning an observable that resolves successfully after a short pause. The redirectUrl property stores the URL that the user wanted to access so you can navigate to it after authentication.

To keep things minimal, this example redirects unauthenticated users to /admin .

Revise the AuthGuard to call the AuthService .

Notice that you inject the AuthService and the Router in the constructor. You haven't provided the AuthService yet but it's good to know that you can inject helpful services into routing guards.

This guard returns a synchronous boolean result. If the user is logged in, it returns true and the navigation continues.

The ActivatedRouteSnapshot contains the future route that will be activated and the RouterStateSnapshot contains the future RouterState of the application, should you pass through the guard check.

If the user is not logged in, you store the attempted URL the user came from using the RouterStateSnapshot.url and tell the router to redirect to a login page—a page you haven't created yet. Returning a UrlTree tells the Router to cancel the current navigation and schedule a new one to redirect the user.

Add the LoginComponent

You need a LoginComponent for the user to log in to the app. After logging in, you'll redirect to the stored URL if available, or use the default URL. There is nothing new about this component or the way you use it in the router configuration.

Register a /login route in the auth/auth-routing.module.ts . In app.module.ts , import and add the AuthModule to the AppModule imports.

CanActivateChild: guarding child routes

You can also protect child routes with the CanActivateChild guard. The CanActivateChild guard is similar to the CanActivate guard. The key difference is that it runs before any child route is activated.

You protected the admin feature module from unauthorized access. You should also protect child routes within the feature module.

Extend the AuthGuard to protect when navigating between the admin routes. Open auth.guard.ts and add the CanActivateChild interface to the imported tokens from the router package.

Next, implement the canActivateChild() method which takes the same arguments as the canActivate() method: an ActivatedRouteSnapshot and RouterStateSnapshot . The canActivateChild() method can return an Observable<boolean|UrlTree> or Promise<boolean|UrlTree> for async checks and a boolean or UrlTree for sync checks. This one returns either true to allow the user to access the admin feature module or UrlTree to redirect the user to the login page instead:

Add the same AuthGuard to the component-less admin route to protect all other child routes at one time instead of adding the AuthGuard to each route individually.

CanDeactivate: handling unsaved changes

Back in the "Heroes" workflow, the app accepts every change to a hero immediately without validation.

In the real world, you might have to accumulate the users changes, validate across fields, validate on the server, or hold changes in a pending state until the user confirms them as a group or cancels and reverts all changes.

When the user navigates away, you can let the user decide what to do with unsaved changes. If the user cancels, you'll stay put and allow more changes. If the user approves, the app can save.

You still might delay navigation until the save succeeds. If you let the user move to the next screen immediately and saving were to fail (perhaps the data is ruled invalid), you would lose the context of the error.

You need to stop the navigation while you wait, asynchronously, for the server to return with its answer.

The CanDeactivate guard helps you decide what to do with unsaved changes and how to proceed.

Cancel and save

Users update crisis information in the CrisisDetailComponent . Unlike the HeroDetailComponent , the user changes do not update the crisis entity immediately. Instead, the app updates the entity when the user presses the Save button and discards the changes when the user presses the Cancel button.

Both buttons navigate back to the crisis list after save or cancel.

In this scenario, the user could click the heroes link, cancel, push the browser back button, or navigate away without saving.

This example app asks the user to be explicit with a confirmation dialog box that waits asynchronously for the user's response.

You could wait for the user's answer with synchronous, blocking code, however, the app is more responsive—and can do other work—by waiting for the user's answer asynchronously.

Generate a Dialog service to handle user confirmation.

Add a confirm() method to the DialogService to prompt the user to confirm their intent. The window.confirm is a blocking action that displays a modal dialog and waits for user interaction.

It returns an Observable that resolves when the user eventually decides what to do: either to discard changes and navigate away ( true ) or to preserve the pending changes and stay in the crisis editor ( false ).

Generate a guard that checks for the presence of a canDeactivate() method in a component—any component.

Paste the following code into your guard.

While the guard doesn't have to know which component has a deactivate method, it can detect that the CrisisDetailComponent component has the canDeactivate() method and call it. The guard not knowing the details of any component's deactivation method makes the guard reusable.

Alternatively, you could make a component-specific CanDeactivate guard for the CrisisDetailComponent . The canDeactivate() method provides you with the current instance of the component , the current ActivatedRoute , and RouterStateSnapshot in case you needed to access some external information. This would be useful if you only wanted to use this guard for this component and needed to get the component's properties or confirm whether the router should allow navigation away from it.

Looking back at the CrisisDetailComponent , it implements the confirmation workflow for unsaved changes.

Notice that the canDeactivate() method can return synchronously; it returns true immediately if there is no crisis or there are no pending changes. But it can also return a Promise or an Observable and the router will wait for that to resolve to truthy (navigate) or falsy (stay on the current route).

Add the Guard to the crisis detail route in crisis-center-routing.module.ts using the canDeactivate array property.

Now you have given the user a safeguard against unsaved changes.

Resolve: pre-fetching component data

In the Hero Detail and Crisis Detail , the app waited until the route was activated to fetch the respective hero or crisis.

If you were using a real world API, there might be some delay before the data to display is returned from the server. You don't want to display a blank component while waiting for the data.

To improve this behavior, you can pre-fetch data from the server using a resolver so it's ready the moment the route is activated. This also allows you to handle errors before routing to the component. There's no point in navigating to a crisis detail for an id that doesn't have a record. It'd be better to send the user back to the Crisis List that shows only valid crisis centers.

In summary, you want to delay rendering the routed component until all necessary data has been fetched.

Fetch data before navigating

At the moment, the CrisisDetailComponent retrieves the selected crisis. If the crisis is not found, the router navigates back to the crisis list view.

The experience might be better if all of this were handled first, before the route is activated. A CrisisDetailResolver service could retrieve a Crisis or navigate away, if the Crisis did not exist, before activating the route and creating the CrisisDetailComponent .

Generate a CrisisDetailResolver service file within the Crisis Center feature area.

Move the relevant parts of the crisis retrieval logic in CrisisDetailComponent.ngOnInit() into the CrisisDetailResolverService . Import the Crisis model, CrisisService , and the Router so you can navigate elsewhere if you can't fetch the crisis.

Be explicit and implement the Resolve interface with a type of Crisis .

Inject the CrisisService and Router and implement the resolve() method. That method could return a Promise , an Observable , or a synchronous return value.

The CrisisService.getCrisis() method returns an observable in order to prevent the route from loading until the data is fetched. The Router guards require an observable to complete , which means it has emitted all of its values. You use the take operator with an argument of 1 to ensure that the Observable completes after retrieving the first value from the Observable returned by the getCrisis() method.

If it doesn't return a valid Crisis , then return an empty Observable , cancel the previous in-progress navigation to the CrisisDetailComponent , and navigate the user back to the CrisisListComponent . The updated resolver service looks like this:

Import this resolver in the crisis-center-routing.module.ts and add a resolve object to the CrisisDetailComponent route configuration.

The CrisisDetailComponent should no longer fetch the crisis. When you re-configured the route, you changed where the crisis is. Update the CrisisDetailComponent to get the crisis from the ActivatedRoute.data.crisis property instead;

Note the following three important points:

  • The router's Resolve interface is optional. The CrisisDetailResolverService doesn't inherit from a base class. The router looks for that method and calls it if found.
  • The router calls the resolver in any case where the user could navigate away so you don't have to code for each use case.
  • Returning an empty Observable in at least one resolver will cancel navigation.

The relevant Crisis Center code for this milestone follows.

Query parameters and fragments

In the route parameters section, you only dealt with parameters specific to the route. However, you can use query parameters to get optional parameters available to all routes.

Fragments refer to certain elements on the page identified with an id attribute.

Update the AuthGuard to provide a session_id query that will remain after navigating to another route.

Add an anchor element so you can jump to a certain point on the page.

Add the NavigationExtras object to the router.navigate() method that navigates you to the /login route.

You can also preserve query parameters and fragments across navigations without having to provide them again when navigating. In the LoginComponent , you'll add an object as the second argument in the router.navigateUrl() function and provide the queryParamsHandling and preserveFragment to pass along the current query parameters and fragment to the next route.

The queryParamsHandling feature also provides a merge option, which preserves and combines the current query parameters with any provided query parameters when navigating.

To navigate to the Admin Dashboard route after logging in, update admin-dashboard.component.ts to handle the query parameters and fragment.

Query parameters and fragments are also available through the ActivatedRoute service. Just like route parameters, the query parameters and fragments are provided as an Observable . The updated Crisis Admin component feeds the Observable directly into the template using the AsyncPipe .

Now, you can click on the Admin button, which takes you to the Login page with the provided queryParamMap and fragment . After you click the login button, notice that you have been redirected to the Admin Dashboard page with the query parameters and fragment still intact in the address bar.

You can use these persistent bits of information for things that need to be provided across pages like authentication tokens or session ids.

The query params and fragment can also be preserved using a RouterLink with the queryParamsHandling and preserveFragment bindings respectively.

Milestone 6: Asynchronous routing

As you've worked through the milestones, the application has naturally gotten larger. At some point you'll reach a point where the application takes a long time to load.

To remedy this issue, use asynchronous routing, which loads feature modules lazily, on request. Lazy loading has multiple benefits.

  • You can load feature areas only when requested by the user.
  • You can speed up load time for users that only visit certain areas of the application.
  • You can continue expanding lazy loaded feature areas without increasing the size of the initial load bundle.

You're already part of the way there. By organizing the application into modules— AppModule , HeroesModule , AdminModule and CrisisCenterModule —you have natural candidates for lazy loading.

Some modules, like AppModule , must be loaded from the start. But others can and should be lazy loaded. The AdminModule , for example, is needed by a few authorized users, so you should only load it when requested by the right people.

Lazy Loading route configuration

Change the admin path in the admin-routing.module.ts from 'admin' to an empty string, , the empty path.

Use empty path routes to group routes together without adding any additional path segments to the URL. Users will still visit /admin and the AdminComponent still serves as the Routing Component containing child routes.

Open the AppRoutingModule and add a new admin route to its appRoutes array.

Give it a loadChildren property instead of a children property. The loadChildren property takes a function that returns a promise using the browser's built-in syntax for lazy loading code using dynamic imports import('...') . The path is the location of the AdminModule (relative to the app root). After the code is requested and loaded, the Promise resolves an object that contains the NgModule , in this case the AdminModule .

Note : When using absolute paths, the NgModule file location must begin with src/app in order to resolve correctly. For custom path mapping with absolute paths , you must configure the baseUrl and paths properties in the project tsconfig.json .

When the router navigates to this route, it uses the loadChildren string to dynamically load the AdminModule . Then it adds the AdminModule routes to its current route configuration. Finally, it loads the requested route to the destination admin component.

The lazy loading and re-configuration happen just once, when the route is first requested; the module and routes are available immediately for subsequent requests.

Angular provides a built-in module loader that supports SystemJS to load modules asynchronously. If you were using another bundling tool, such as Webpack, you would use the Webpack mechanism for asynchronously loading modules.

Take the final step and detach the admin feature set from the main application. The root AppModule must neither load nor reference the AdminModule or its files.

In app.module.ts , remove the AdminModule import statement from the top of the file and remove the AdminModule from the NgModule's imports array.

CanLoad: guarding unauthorized loading of feature modules

You're already protecting the AdminModule with a CanActivate guard that prevents unauthorized users from accessing the admin feature area. It redirects to the login page if the user is not authorized.

But the router is still loading the AdminModule even if the user can't visit any of its components. Ideally, you'd only load the AdminModule if the user is logged in.

Add a CanLoad guard that only loads the AdminModule once the user is logged in and attempts to access the admin feature area.

The existing AuthGuard already has the essential logic in its checkLogin() method to support the CanLoad guard.

Open auth.guard.ts . Import the CanLoad interface from @angular/router . Add it to the AuthGuard class's implements list. Then implement canLoad() as follows:

The router sets the canLoad() method's route parameter to the intended destination URL. The checkLogin() method redirects to that URL once the user has logged in.

Now import the AuthGuard into the AppRoutingModule and add the AuthGuard to the canLoad array property for the admin route. The completed admin route looks like this:

Preloading: background loading of feature areas

In addition to loading modules on-demand, you can load modules asynchronously with preloading.

The AppModule is eagerly loaded when the application starts, meaning that it loads right away. Now the AdminModule loads only when the user clicks on a link, which is called lazy loading.

Preloading allows you to load modules in the background so that the data is ready to render when the user activates a particular route. Consider the Crisis Center. It isn't the first view that a user sees. By default, the Heroes are the first view. For the smallest initial payload and fastest launch time, you should eagerly load the AppModule and the HeroesModule .

You could lazy load the Crisis Center. But you're almost certain that the user will visit the Crisis Center within minutes of launching the app. Ideally, the app would launch with just the AppModule and the HeroesModule loaded and then, almost immediately, load the CrisisCenterModule in the background. By the time the user navigates to the Crisis Center, its module will have been loaded and ready.

How preloading works

After each successful navigation, the router looks in its configuration for an unloaded module that it can preload. Whether it preloads a module, and which modules it preloads, depends upon the preload strategy.

The Router offers two preloading strategies:

  • No preloading, which is the default. Lazy loaded feature areas are still loaded on-demand.
  • Preloading of all lazy loaded feature areas.

The router either never preloads, or preloads every lazy loaded module. The Router also supports custom preloading strategies for fine control over which modules to preload and when.

This section guides you through updating the CrisisCenterModule to load lazily by default and use the PreloadAllModules strategy to load all lazy loaded modules.

Lazy load the crisis center

Update the route configuration to lazy load the CrisisCenterModule . Take the same steps you used to configure AdminModule for lazy loading.

  • Change the crisis-center path in the CrisisCenterRoutingModule to an empty string.
  • Add a crisis-center route to the AppRoutingModule .
  • Set the loadChildren string to load the CrisisCenterModule .
  • Remove all mention of the CrisisCenterModule from app.module.ts .

Here are the updated modules before enabling preload :

You could try this now and confirm that the CrisisCenterModule loads after you click the "Crisis Center" button.

To enable preloading of all lazy loaded modules, import the PreloadAllModules token from the Angular router package.

The second argument in the RouterModule.forRoot() method takes an object for additional configuration options. The preloadingStrategy is one of those options. Add the PreloadAllModules token to the forRoot() call:

This configures the Router preloader to immediately load all lazy loaded routes (routes with a loadChildren property).

When you visit http://localhost:4200 , the /heroes route loads immediately upon launch and the router starts loading the CrisisCenterModule right after the HeroesModule loads.

Currently, the AdminModule does not preload because CanLoad is blocking it.

CanLoad blocks preload

The PreloadAllModules strategy does not load feature areas protected by a CanLoad guard.

You added a CanLoad guard to the route in the AdminModule a few steps back to block loading of that module until the user is authorized. That CanLoad guard takes precedence over the preload strategy.

If you want to preload a module as well as guard against unauthorized access, remove the canLoad() guard method and rely on the canActivate() guard alone.

Custom Preloading Strategy

Preloading every lazy loaded module works well in many situations. However, in consideration of things such as low bandwidth and user metrics, you can use a custom preloading strategy for specific feature modules.

This section guides you through adding a custom strategy that only preloads routes whose data.preload flag is set to true . Recall that you can add anything to the data property of a route.

Set the data.preload flag in the crisis-center route in the AppRoutingModule .

Generate a new SelectivePreloadingStrategy service.

Replace the contents of selective-preloading-strategy.service.ts with the following:

SelectivePreloadingStrategyService implements the PreloadingStrategy , which has one method, preload() .

The router calls the preload() method with two arguments:

  • The route to consider.
  • A loader function that can load the routed module asynchronously.

An implementation of preload must return an Observable . If the route does preload, it returns the observable returned by calling the loader function. If the route does not preload, it returns an Observable of null .

In this sample, the preload() method loads the route if the route's data.preload flag is truthy.

As a side-effect, SelectivePreloadingStrategyService logs the path of a selected route in its public preloadedModules array.

Shortly, you'll extend the AdminDashboardComponent to inject this service and display its preloadedModules array.

But first, make a few changes to the AppRoutingModule .

  • Import SelectivePreloadingStrategyService into AppRoutingModule .
  • Replace the PreloadAllModules strategy in the call to forRoot() with this SelectivePreloadingStrategyService .

Now edit the AdminDashboardComponent to display the log of preloaded routes.

  • Import the SelectivePreloadingStrategyService .
  • Inject it into the dashboard's constructor.
  • Update the template to display the strategy service's preloadedModules array.

Now the file is as follows:

Once the application loads the initial route, the CrisisCenterModule is preloaded. Verify this by logging in to the Admin feature area and noting that the crisis-center is listed in the Preloaded Modules . It also logs to the browser's console.

Migrating URLs with redirects

You've setup the routes for navigating around your application and used navigation imperatively and declaratively. But like any application, requirements change over time. You've setup links and navigation to /heroes and /hero/:id from the HeroListComponent and HeroDetailComponent components. If there were a requirement that links to heroes become superheroes , you would still want the previous URLs to navigate correctly. You also don't want to update every link in your application, so redirects makes refactoring routes trivial.

Changing /heroes to /superheroes

This section guides you through migrating the Hero routes to new URLs. The Router checks for redirects in your configuration before navigating, so each redirect is triggered when needed. To support this change, add redirects from the old routes to the new routes in the heroes-routing.module .

Notice two different types of redirects. The first change is from /heroes to /superheroes without any parameters. The second change is from /hero/:id to /superhero/:id , which includes the :id route parameter. Router redirects also use powerful pattern-matching, so the Router inspects the URL and replaces route parameters in the path with their appropriate destination. Previously, you navigated to a URL such as /hero/15 with a route parameter id of 15 .

The Router also supports query parameters and the fragment when using redirects. When using absolute redirects, the Router will use the query parameters and the fragment from the redirectTo in the route config. When using relative redirects, the Router use the query params and the fragment from the source URL.

Currently, the empty path route redirects to /heroes , which redirects to /superheroes . This won't work because the Router handles redirects once at each level of routing configuration. This prevents chaining of redirects, which can lead to endless redirect loops.

Instead, update the empty path route in app-routing.module.ts to redirect to /superheroes .

A routerLink isn't tied to route configuration, so update the associated router links to remain active when the new route is active. Update the app.component.ts template for the /heroes routerLink .

Update the goToHeroes() method in the hero-detail.component.ts to navigate back to /superheroes with the optional route parameters.

With the redirects setup, all previous routes now point to their new destinations and both URLs still function as intended.

Inspect the router's configuration

To determine if your routes are actually evaluated in the proper order , you can inspect the router's configuration.

Do this by injecting the router and logging to the console its config property. For example, update the AppModule as follows and look in the browser console window to see the finished route configuration.

For the completed router app, see the for the final source code.

© 2010–2021 Google, Inc. Licensed under the Creative Commons Attribution License 4.0. https://angular.io/guide/router-tutorial-toh

  • Angular latest documentation

Powered by MediaWiki

This website requires JavaScript.

Este sitio web requiere JavaScript.

IMAGES

  1. Angular Router Tutorial: Setting Up Routing in Your Application

    angular router tour

  2. Angular Router: An Introduction to Component Routing

    angular router tour

  3. Angular Router

    angular router tour

  4. Angular Router Tutorial: Learn Configure Routing & Navigation Service

    angular router tour

  5. Angular Router Series: Pillar 2

    angular router tour

  6. Angular Material using Angular Router with Lazy Loading and Named Outlets

    angular router tour

VIDEO

  1. 5.5 Скринкаст по Angular

  2. TECHWIZ Angular Router

  3. Deploy angular application using Router 53, CloudFront, API Gateway, S3, Lambda, DynamoDB

  4. 18. What Is Router Outlet In Angular

  5. 9. Component Part-2

  6. Angular Router Phần 1: Config Các Thành Phần Cơ Bản

COMMENTS

  1. Angular

    Router tutorial: tour of heroes. This tutorial provides an extensive overview of the Angular router. In this tutorial, you build upon a basic router configuration to explore features such as child routes, route parameters, lazy load NgModules, guard routes, and preloading data to improve the user experience. For a working example of the final ...

  2. Angular

    To add this functionality to your sample application, you need to update the app.config.ts file to use the router providers function, provideRouter . You import this provider function from @angular/router. From your code editor, open the app.config.ts file. Add the following import statements:

  3. Angular

    In Angular, the best practice is to load and configure the router in a separate, top-level module. The router is dedicated to routing and imported by the root AppModule. By convention, the module class name is AppRoutingModule and it belongs in the app-routing.module.ts in the src/app directory. Run ng generate to create the application routing ...

  4. Angular Router: Child Routes, Auxiliary Routes, Master Detail

    In this post, we are going to do a guided tour of the main routing configuration concepts needed to use the Angular Router effectively. The goal is to get a solid initial understanding of the Angular Router, before presenting a more advanced example. This post is the first of a series on the Angular Router, here is the complete series:

  5. Router Tutorial: Tour of Heroes

    Router tutorial: tour of heroes. This tutorial provides an extensive overview of the Angular router. In this tutorial, you build upon a basic router configuration to explore features such as child routes, route parameters, lazy load NgModules, guard routes, and preloading data to improve the user experience. For a working example of the final ...

  6. A Complete Guide To Routing In Angular

    Introducing Angular Router. Angular Router is a powerful JavaScript router built and maintained by the Angular core team that can be installed from the @angular/router package. It provides a complete routing library with the possibility to have multiple router outlets, different path matching strategies, easy access to route parameters and ...

  7. Routing & Navigation

    You need two pieces of information: the routing path to the component and the hero's id. Accordingly, the link parameters array has two items: the routing path and a route parameter that specifies the id of the selected hero. The router composes the destination URL from the array like this: localhost:3000/hero/15.

  8. Introduction to Angular Routing

    This article has been updated to the latest version Angular 17 and tested with Angular 16. The content is likely still applicable for all Angular 2 + versions. Angular brings many improved modules to the Angular ecosystem including a new router called the Component Router. The Component Router is a highly configurable and feature packed router.

  9. Ngx-Tour

    This is a product tour library built with Angular (2+). It's inspired by angular-ui-tour. TourMatMenuModule is an implementation of the tour ui that uses Angular Material MatMenu to display tour steps. Installation. npm install ngx-tour-core ngx-tour-md-menu @angular/animations @angular/cdk @angular/http @angular/material;

  10. Angular Routing Tutorial with Example

    Angular Routing Tutorial with Example - TekTutorialsHubIf you want to learn how to use Angular Router to implement navigation in an Angular app, this tutorial is for you. You will learn how to create routes, link them to components, use route parameters, guards, resolvers, and more. This tutorial also covers the basics of Angular CLI, which is a powerful tool for creating and managing Angular ...

  11. New Angular Router

    router.config({ path: '/', component: '/user' }); For more, see the configuration guide. navigate. Navigate to a URL. Returns the cannonical URL for the route navigated to. renavigate. Navigates to either the last URL successfully naviagted to, or the last URL requested if the router has yet to successfully navigate.

  12. Angular

    Angular is a platform for building mobile and desktop web applications. ... adding routing to Tour of Heroes. Router reference. Forms. Introduction. Reactive forms. ... This listener detects navigations triggered from outside the Router (the browser back/forward buttons, for example) and schedules a corresponding Router navigation so that the ...

  13. Router tutorial: tour of heroes

    Router tutorial: tour of heroes. This tutorial provides an extensive overview of the Angular router. In this tutorial, you will build upon a basic router configuration to explore features such as child routes, route parameters, lazy load NgModules, guard routes, and preloading data to improve the user experience.

  14. Angular

    Animating this route transition can greatly enhance the user experience. The Angular router comes with high-level animation functions that let you animate the transitions between views when a route changes. To produce an animation sequence when switching between routes, you need to define nested animation sequences.

  15. angular

    1. I based my work off of the tutorial Tour of Heroes tutorial as presented on Angular.io. The app was created with the following command: ng new toh --no-standalone --routing --ssr=false. I understand that "standalone" is the new default, but I opted to do without it since my company is using 15.2.2.

  16. Angular

    Description link. Given a route configuration [{ path: 'user/:name', component: UserCmp }] , the following creates a static link to the route: <a routerLink ="/user/bob">link to user component</a>. You can use dynamic values to generate the link. For a dynamic link, pass an array of path segments, followed by the params for each segment.

  17. Angular

    Router tutorial: tour of heroes. This tutorial provides an extensive overview of the Angular router. In this tutorial, you will build upon a basic router configuration to explore features such as child routes, route parameters, lazy load NgModules, guard routes, and preloading data to improve the user experience.