4306 lines
135 KiB
Markdown
4306 lines
135 KiB
Markdown
# Routing & Navigation
|
|
|
|
The Angular **`Router`** enables navigation from one [view](guide/glossary#view) to the next
|
|
as users perform application tasks.
|
|
|
|
This guide covers the router's primary features, illustrating them through the evolution
|
|
of a small application that you can <live-example>run live in the browser</live-example>.
|
|
|
|
<!-- style for all tables on this page -->
|
|
<style>
|
|
td, th {vertical-align: top}
|
|
</style>
|
|
|
|
|
|
## Overview
|
|
|
|
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.
|
|
|
|
{@a basics}
|
|
|
|
|
|
## The Basics
|
|
|
|
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.
|
|
|
|
|
|
{@a basics-base-href}
|
|
|
|
|
|
### *<base href>*
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/index.html" linenums="false" title="src/index.html (base-href)" region="base-href">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
{@a basics-router-imports}
|
|
|
|
|
|
### Router imports
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/app.module.1.ts" linenums="false" title="src/app/app.module.ts (import)" region="import-router">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
You'll learn about more options in the [details below](#browser-url-styles).
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a basics-config}
|
|
|
|
|
|
### Configuration
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/app.module.0.ts" linenums="false" title="src/app/app.module.ts (excerpt)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
{@a example-config}
|
|
|
|
|
|
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 second 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](#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.
|
|
|
|
If you need to see what events are happening during the navigation lifecycle, there is the **enableTracing** option as part of the router's default configuration. This outputs each router event that took place during each navigation lifecycle to the browser console. This should only be used for _debugging_ purposes. You set the `enableTracing: true` option in the object passed as the second argument to the `RouterModule.forRoot()` method.
|
|
|
|
{@a basics-router-outlet}
|
|
|
|
|
|
### Router outlet
|
|
|
|
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.
|
|
|
|
|
|
<code-example language="html">
|
|
<router-outlet></router-outlet>
|
|
<!-- Routed views go here -->
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
{@a basics-router-links}
|
|
|
|
|
|
### Router links
|
|
|
|
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:
|
|
|
|
|
|
<code-example path="router/src/app/app.component.1.ts" linenums="false" title="src/app/app.component.ts (template)" region="template">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
{@a basics-router-state}
|
|
|
|
|
|
### Router state
|
|
|
|
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.
|
|
|
|
### Router events
|
|
|
|
During each navigation, the `Router` emits navigation events through the `Router.events` property. These events range from when the navigation starts and ends to many points in between. The full list of navigation events is displayed in the table below.
|
|
|
|
<table>
|
|
<tr>
|
|
<th>
|
|
Router Event
|
|
</th>
|
|
|
|
<th>
|
|
Description
|
|
</th>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>NavigationStart</code>
|
|
</td>
|
|
<td>
|
|
|
|
An [event](api/router/NavigationStart) triggered when navigation starts.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>RoutesRecognized</code>
|
|
</td>
|
|
<td>
|
|
|
|
An [event](api/router/RoutesRecognized) triggered when the Router parses the URL and the routes are recognized.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>RouteConfigLoadStart</code>
|
|
</td>
|
|
<td>
|
|
|
|
An [event](api/router/RouteConfigLoadStart) triggered before the `Router`
|
|
[lazy loads](#asynchronous-routing) a route configuration.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>RouteConfigLoadEnd</code>
|
|
</td>
|
|
<td>
|
|
|
|
An [event](api/router/RouteConfigLoadEnd) triggered after a route has been lazy loaded.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>NavigationEnd</code>
|
|
</td>
|
|
<td>
|
|
|
|
An [event](api/router/NavigationEnd) triggered when navigation ends successfully.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>NavigationCancel</code>
|
|
</td>
|
|
<td>
|
|
|
|
An [event](api/router/NavigationCancel) triggered when navigation is canceled.
|
|
This is due to a [Route Guard](#guards) returning false during navigation.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>NavigationError</code>
|
|
</td>
|
|
<td>
|
|
|
|
An [event](api/router/NavigationError) triggered when navigation fails due to an unexpected error.
|
|
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
These events are logged to the console when the `enableTracing` option is enabled also. Since the events are provided as an `Observable`, you can `filter()` for events of interest and `subscribe()` to them to make decisions based on the sequence of events in the navigation process.
|
|
|
|
|
|
{@a basics-summary}
|
|
|
|
|
|
### Summary
|
|
|
|
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:
|
|
|
|
<table>
|
|
|
|
<tr>
|
|
|
|
<th>
|
|
Router Part
|
|
</th>
|
|
|
|
<th>
|
|
Meaning
|
|
</th>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
<code>Router</code>
|
|
</td>
|
|
|
|
<td>
|
|
Displays the application component for the active URL.
|
|
Manages navigation from one component to the next.
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
<code>RouterModule</code>
|
|
</td>
|
|
|
|
<td>
|
|
A separate NgModule that provides the necessary service providers
|
|
and directives for navigating through application views.
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
<code>Routes</code>
|
|
</td>
|
|
|
|
<td>
|
|
Defines an array of Routes, each mapping a URL path to a component.
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
<code>Route</code>
|
|
</td>
|
|
|
|
<td>
|
|
Defines how the router should navigate to a component based on a URL pattern.
|
|
Most routes consist of a path and a component type.
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
<code>RouterOutlet</code>
|
|
</td>
|
|
|
|
<td>
|
|
The directive (<code><router-outlet></code>) that marks where the router displays a view.
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
<code>RouterLink</code>
|
|
</td>
|
|
|
|
<td>
|
|
The directive for binding a clickable HTML element to
|
|
a route. Clicking an element with a <code>routerLink</code> directive
|
|
that is bound to a <i>string</i> or a <i>link parameters array</i> triggers a navigation.
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
<code>RouterLinkActive</code>
|
|
</td>
|
|
|
|
<td>
|
|
The directive for adding/removing classes from an HTML element when an associated
|
|
<code>routerLink</code> contained on or inside the element becomes active/inactive.
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
<code>ActivatedRoute</code>
|
|
</td>
|
|
|
|
<td>
|
|
A service that is provided to each route component that contains route specific
|
|
information such as route parameters, static data, resolve data, global query params, and the global fragment.
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
<code>RouterState</code>
|
|
</td>
|
|
|
|
<td>
|
|
The current state of the router including a tree of the currently activated
|
|
routes together with convenience methods for traversing the route tree.
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
<b><i>Link parameters array</i></b>
|
|
</td>
|
|
|
|
<td>
|
|
An array that the router interprets as a routing instruction.
|
|
You can bind that array to a <code>RouterLink</code> or pass the array as an argument to
|
|
the <code>Router.navigate</code> method.
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
<b><i>Routing component</i></b>
|
|
</td>
|
|
|
|
<td>
|
|
An Angular component with a <code>RouterOutlet</code> that displays views based on router navigations.
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
{@a sample-app-intro}
|
|
|
|
|
|
## The sample application
|
|
|
|
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 full source for the final version of the app can be seen and downloaded from the <live-example></live-example>.
|
|
|
|
|
|
### 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:
|
|
|
|
1. A *Crisis Center* for maintaining the list of crises for assignment to heroes.
|
|
1. A *Heroes* area for maintaining the list of heroes employed by the agency.
|
|
1. An *Admin* area to manage the list of crises and heroes.
|
|
|
|
Try it by clicking on this <live-example title="Hero Employment Agency Live Example">live example link</live-example>.
|
|
|
|
Once the app warms up, you'll see a row of navigation buttons
|
|
and the *Heroes* view with its list of heroes.
|
|
|
|
|
|
<figure>
|
|
<img src='generated/images/guide/router/hero-list.png' alt="Hero List">
|
|
</figure>
|
|
|
|
|
|
|
|
Select one hero and the app takes you to a hero editing screen.
|
|
|
|
<figure>
|
|
<img src='generated/images/guide/router/hero-detail.png' alt="Crisis Center Detail">
|
|
</figure>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<figure>
|
|
<img src='generated/images/guide/router/crisis-center-list.png' alt="Crisis Center List">
|
|
</figure>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<figure>
|
|
<img src='generated/images/guide/router/crisis-center-detail.png' alt="Crisis Center Detail">
|
|
</figure>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<figure>
|
|
<img src='generated/images/guide/router/confirm-dialog.png' alt="Confirm Dialog">
|
|
</figure>
|
|
|
|
|
|
|
|
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.
|
|
|
|
{@a getting-started}
|
|
|
|
## Milestone 1: Getting started with the router
|
|
|
|
Begin with a simple version of the app that navigates between two empty views.
|
|
|
|
<figure>
|
|
<img src='generated/images/guide/router/router-1-anim.gif' alt="App in action">
|
|
</figure>
|
|
|
|
|
|
|
|
{@a base-href}
|
|
|
|
|
|
### Set the *<base href>*
|
|
|
|
The router uses the browser's
|
|
<a href="https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries" title="HTML5 browser history push-state">history.pushState</a>
|
|
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.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
HTML5 style navigation is the router default.
|
|
In the [LocationStrategy and browser URL styles](#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.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
You must **add a
|
|
<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base" title="base href"><base href> element</a>**
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/index.html" linenums="false" title="src/index.html (base-href)" region="base-href">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
<div class="callout is-important">
|
|
|
|
|
|
|
|
<header>
|
|
Live example note
|
|
</header>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<code-example language="html">
|
|
<script>document.write('<base href="' + document.location + '" />');</script>
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
You only need this trick for the live example, not production code.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a import}
|
|
|
|
|
|
### Importing from the router library
|
|
|
|
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 route-config}
|
|
|
|
|
|
#### Define 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](api/router/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.
|
|
|
|
|
|
<code-example path="router/src/app/app.module.1.ts" linenums="false" title="src/app/app.module.ts (first-config)" region="first-config">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
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](#routing-module)**, a special type of `Service Module` dedicated to the purpose
|
|
of routing in feature modules.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
Providing the `RouterModule` in the `AppModule` makes the Router available everywhere in the application.
|
|
|
|
|
|
{@a shell}
|
|
|
|
|
|
### The *AppComponent* shell
|
|
|
|
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:
|
|
|
|
|
|
<figure>
|
|
<img src='generated/images/guide/router/shell-and-outlet.png' alt="Shell">
|
|
</figure>
|
|
|
|
|
|
|
|
{@a shell-template}
|
|
|
|
|
|
The corresponding component template looks like this:
|
|
|
|
|
|
<code-example path="router/src/app/app.component.1.ts" linenums="false" title="src/app/app.component.ts (template)" region="template">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
{@a router-outlet}
|
|
|
|
|
|
### *RouterOutlet*
|
|
|
|
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.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
The router adds the `<router-outlet>` element to the DOM
|
|
and subsequently inserts the navigated view element
|
|
immediately _after_ the `<router-outlet>`.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a router-link}
|
|
|
|
|
|
### *RouterLink* binding
|
|
|
|
Above the outlet, within the anchor tags, you see
|
|
[attribute bindings](guide/template-syntax#attribute-binding) 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.
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
Learn about the how you can also use the _link parameters array_ in the [appendix below](#link-parameters-array).
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a router-link-active}
|
|
|
|
|
|
### *RouterLinkActive* binding
|
|
|
|
On each anchor tag, you also see [property bindings](guide/template-syntax#property-binding) 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.
|
|
|
|
|
|
{@a router-directives}
|
|
|
|
|
|
### *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:
|
|
|
|
|
|
<code-example path="router/src/app/app.component.1.ts" linenums="false" title="src/app/app.component.ts (excerpt)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
{@a wildcard}
|
|
|
|
|
|
### 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.
|
|
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](#redirect) to an existing route.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
The router selects the route with a [_first match wins_](#example-config) strategy.
|
|
Wildcard routes are the least specific routes in the route configuration.
|
|
Be sure it is the _last_ route in the configuration.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
To test this feature, add a button with a `RouterLink` to the `HeroListComponent` template and set the link to `"/sidekicks"`.
|
|
|
|
<code-example path="router/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (excerpt)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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`.
|
|
|
|
<code-example path="router/src/app/app.module.1.ts" linenums="false" title="src/app/app.module.ts (wildcard)" region="wildcard">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Create the `PageNotFoundComponent` to display when users visit invalid URLs.
|
|
|
|
<code-example path="router/src/app/not-found.component.ts" linenums="false" title="src/app/not-found.component.ts (404 component)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
{@a default-route}
|
|
|
|
|
|
### The _default_ route to heroes
|
|
|
|
When the application launches, the initial URL in the browser bar is something like:
|
|
|
|
<code-example>
|
|
localhost:3000
|
|
</code-example>
|
|
|
|
That doesn't match any of the concrete configured 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:3000/heroes` into the address bar.
|
|
|
|
{@a redirect}
|
|
|
|
### 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.
|
|
|
|
|
|
<code-example path="router/src/app/app-routing.module.1.ts" linenums="false" title="src/app/app-routing.module.ts (appRoutes)" region="appRoutes">
|
|
</code-example>
|
|
|
|
|
|
|
|
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'`.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
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](http://victorsavkin.com/post/146722301646/angular-router-empty-paths-componentless-routes).
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
### 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:
|
|
|
|
|
|
<div class='filetree'>
|
|
|
|
<div class='file'>
|
|
router-sample
|
|
</div>
|
|
|
|
<div class='children'>
|
|
|
|
<div class='file'>
|
|
src
|
|
</div>
|
|
|
|
<div class='children'>
|
|
|
|
<div class='file'>
|
|
app
|
|
</div>
|
|
|
|
<div class='children'>
|
|
|
|
<div class='file'>
|
|
app.component.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
app.module.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
crisis-list.component.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
hero-list.component.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
not-found.component.ts
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class='file'>
|
|
main.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
index.html
|
|
</div>
|
|
|
|
<div class='file'>
|
|
styles.css
|
|
</div>
|
|
|
|
<div class='file'>
|
|
tsconfig.json
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class='file'>
|
|
node_modules ...
|
|
</div>
|
|
|
|
<div class='file'>
|
|
package.json
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
Here are the files discussed in this milestone.
|
|
|
|
|
|
<code-tabs>
|
|
|
|
<code-pane title="app.component.ts" path="router/src/app/app.component.1.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="app.module.ts" path="router/src/app/app.module.1.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="main.ts" path="router/src/main.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="hero-list.component.ts" path="router/src/app/hero-list.component.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="crisis-list.component.ts" path="router/src/app/crisis-list.component.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="not-found.component.ts" path="router/src/app/not-found.component.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="index.html" path="router/src/index.html">
|
|
|
|
</code-pane>
|
|
|
|
</code-tabs>
|
|
|
|
|
|
{@a routing-module}
|
|
|
|
## 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](guide/ngmodule-faq#routing-module).
|
|
|
|
|
|
{@a routing-refactor}
|
|
|
|
|
|
### Refactor the routing configuration into a _routing module_
|
|
|
|
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.
|
|
|
|
<code-example path="router/src/app/app-routing.module.1.ts" title="src/app/app-routing.module.ts">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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`.
|
|
|
|
<code-example path="router/src/app/app.module.2.ts" title="src/app/app.module.ts">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
Later in this guide you will create [multiple routing modules](#hero-routing-module) and discover that
|
|
you must import those routing modules [in the correct order](#routing-module-order).
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
The application continues to work just the same, and you can use `AppRoutingModule` as
|
|
the central place to maintain future routing configuration.
|
|
|
|
|
|
{@a why-routing-module}
|
|
|
|
|
|
### 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.
|
|
|
|
{@a heroes-feature}
|
|
|
|
## 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.
|
|
|
|
This example recreates the heroes feature in the "Services" episode of the
|
|
[Tour of Heroes tutorial](tutorial/toh-pt4 "Tour of Heroes: Services"),
|
|
and you'll be copying much of the code
|
|
from the <live-example name="toh-pt4" title="Tour of Heroes: Services example code"></live-example>.
|
|
|
|
Here's how the user will experience this version of the app:
|
|
|
|
|
|
<figure>
|
|
<img src='generated/images/guide/router/router-2-anim.gif' alt="App in action">
|
|
</figure>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
{@a heroes-functionality}
|
|
|
|
|
|
### Add heroes functionality
|
|
|
|
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`.
|
|
* Copy into it the contents of the `app.component.ts` from
|
|
the <live-example name="toh-pt4" title="Tour of Heroes: Services example code">"Services" tutorial</live-example>.
|
|
* Make a few minor but necessary changes:
|
|
|
|
* 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:
|
|
|
|
|
|
<code-example path="router/src/app/heroes/heroes.module.ts" region="v1" title="src/app/heroes/heroes.module.ts (pre-routing)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
When you're done, you'll have these *hero management* files:
|
|
|
|
|
|
<div class='filetree'>
|
|
|
|
<div class='file'>
|
|
src/app/heroes
|
|
</div>
|
|
|
|
<div class='children'>
|
|
|
|
<div class='file'>
|
|
hero-detail.component.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
hero-list.component.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
hero.service.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
heroes.module.ts
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a hero-routing-requirements}
|
|
|
|
|
|
### *Hero* feature routing requirements
|
|
|
|
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.
|
|
|
|
|
|
{@a hero-routing-module}
|
|
|
|
|
|
### *Hero* feature route configuration
|
|
|
|
Create a new `heroes-routing.module.ts` in the `heroes` folder
|
|
using the same techniques you learned while creating the `AppRoutingModule`.
|
|
|
|
|
|
<code-example path="router/src/app/heroes/heroes-routing.module.ts" title="src/app/heroes/heroes-routing.module.ts">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
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.
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a adding-routing-module}
|
|
|
|
|
|
### Add the routing module to the _HeroesModule_
|
|
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:
|
|
|
|
|
|
<code-example path="router/src/app/heroes/heroes.module.ts" title="src/app/heroes/heroes.module.ts">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
{@a remove-duplicate-hero-routes}
|
|
|
|
|
|
### 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.
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/app-routing.module.2.ts" linenums="false" title="src/app/app-routing.module.ts (v2)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
{@a merge-hero-routes}
|
|
|
|
|
|
### Import hero module into AppModule
|
|
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:
|
|
|
|
|
|
<code-example path="router/src/app/app.module.3.ts" title="src/app/app.module.ts">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
{@a routing-module-order}
|
|
|
|
|
|
### Module import order matters
|
|
|
|
Look at the module `imports` array. Notice that the `AppRoutingModule` is _last_.
|
|
Most importantly, it comes _after_ the `HeroesModule`.
|
|
|
|
<code-example path="router/src/app/app.module.3.ts" region="module-imports" title="src/app/app.module.ts (module-imports)" linenums="false">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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](#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.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
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](#inspect-config "Inspect the router config").
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a route-def-with-parameter}
|
|
|
|
|
|
### Route definition with a parameter
|
|
|
|
Return to the `HeroesRoutingModule` and look at the route definitions again.
|
|
The route to `HeroDetailComponent` has a twist.
|
|
|
|
|
|
<code-example path="router/src/app/heroes/heroes-routing.module.ts" linenums="false" title="src/app/heroes/heroes-routing.module.ts (excerpt)" region="hero-detail-route">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
<code-example format="nocode">
|
|
localhost:3000/hero/15
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<div class="callout is-helpful">
|
|
|
|
|
|
|
|
<header>
|
|
Route parameter: Required or optional?
|
|
</header>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a navigate}
|
|
|
|
|
|
### Navigate to hero detail imperatively
|
|
|
|
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:
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-list.component.1.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (constructor)" region="ctor">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Make the following few changes to the component's template:
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-list.component.1.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (template)" region="template">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
The template defines an `*ngFor` repeater such as [you've seen before](guide/displaying-data#ngFor).
|
|
There's a `(click)` [event binding](guide/template-syntax#event-binding) to the component's
|
|
`onSelect` method which you implement as follows:
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-list.component.1.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (select)" region="select">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
{@a route-parameters}
|
|
|
|
|
|
### 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.
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-list.component.1.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (link-parameters-array)" region="link-parameters-array">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
The router composes the destination URL from the array like this:
|
|
`localhost:3000/hero/15`.
|
|
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a activated-route}
|
|
|
|
|
|
### ActivatedRoute: the one-stop-shop for route information
|
|
|
|
The route path and parameters are available through an injected router service called the
|
|
[ActivatedRoute](api/router/ActivatedRoute).
|
|
It has a great deal of useful information including:
|
|
|
|
<table>
|
|
<tr>
|
|
<th>
|
|
Property
|
|
</th>
|
|
|
|
<th>
|
|
Description
|
|
</th>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>url</code>
|
|
</td>
|
|
<td>
|
|
|
|
An `Observable` of the route path(s), represented as an array of strings for each part of the route path.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>data</code>
|
|
</td>
|
|
<td>
|
|
|
|
An `Observable` that contains the `data` object provided for the route. Also contains any resolved values from the [resolve guard](#resolve-guard).
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>paramMap</code>
|
|
</td>
|
|
<td>
|
|
|
|
An `Observable` that contains a [map](api/router/ParamMap) of the required and [optional parameters](#optional-route-parameters) specific to the route. The map supports retrieving single and multiple values from the same parameter.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>queryParamMap</code>
|
|
</td>
|
|
<td>
|
|
|
|
An `Observable` that contains a [map](api/router/ParamMap) of the [query parameters](#query-parameters) available to all routes.
|
|
The map supports retrieving single and multiple values from the query parameter.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>fragment</code>
|
|
</td>
|
|
<td>
|
|
|
|
An `Observable` of the URL [fragment](#fragment) available to all routes.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>outlet</code>
|
|
</td>
|
|
<td>
|
|
|
|
The name of the `RouterOutlet` used to render the route. For an unnamed outlet, the outlet name is _primary_.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>routeConfig</code>
|
|
</td>
|
|
<td>
|
|
|
|
The route configuration used for the route that contains the origin path.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>parent</code>
|
|
</td>
|
|
<td>
|
|
|
|
The route's parent `ActivatedRoute` when this route is a [child route](#child-routing-component).
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>firstChild</code>
|
|
</td>
|
|
<td>
|
|
|
|
Contains the first `ActivatedRoute` in the list of this route's child routes.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>children</code>
|
|
</td>
|
|
<td>
|
|
|
|
Contains all the [child routes](#child-routing-component) activated under the current route.
|
|
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<div class="l-sub-section">
|
|
|
|
Two older properties are still available. They are less capable than their replacements, discouraged, and may be deprecated in a future Angular version.
|
|
|
|
**`params`** — An `Observable` that contains the required and [optional parameters](#optional-route-parameters) specific to the route. Use `paramMap` instead.
|
|
|
|
**`queryParams`** — An `Observable` that contains the [query parameters](#query-parameters) available to all routes.
|
|
Use `queryParamMap` instead.
|
|
|
|
</div>
|
|
|
|
#### _Activated Route_ in action
|
|
|
|
Import the `Router`, `ActivatedRoute`, and `ParamMap` tokens from the router package.
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-detail.component.1.ts" linenums="false" title="src/app/heroes/hero-detail.component.ts (activated route)" region="imports">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Import the `switchMap` operator because you need it later to process the `Observable` route parameters.
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-detail.component.ts" linenums="false" title="src/app/heroes/hero-detail.component.ts (switchMap operator import)" region="rxjs-operator-import">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
{@a hero-detail-ctor}
|
|
|
|
|
|
As usual, you write a constructor that asks Angular to inject services
|
|
that the component requires and reference them as private variables.
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-detail.component.ts" linenums="false" title="src/app/heroes/hero-detail.component.ts (constructor)" region="ctor">
|
|
|
|
</code-example>
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-detail.component.ts" linenums="false" title="src/app/heroes/hero-detail.component.ts (ngOnInit)" region="ngOnInit">
|
|
|
|
</code-example>
|
|
|
|
The `paramMap` processing is a bit tricky. When the map changes, you `get()`
|
|
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.
|
|
|
|
You might think to use the RxJS `map` operator.
|
|
But the `HeroService` returns an `Observable<Hero>`.
|
|
Your subscription wants the `Hero`, not an `Observable<Hero>`.
|
|
So you flatten the `Observable` with the `switchMap` operator instead.
|
|
|
|
The `switchMap` operator also cancels previous in-flight 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`.
|
|
|
|
Finally, you activate the observable with `subscribe` method and (re)set the component's `hero` property with the retrieved hero.
|
|
|
|
#### _ParamMap_ API
|
|
|
|
The `ParamMap` API is inspired by the [URLSearchParams interface](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParamsOPut). It provides methods
|
|
to handle parameter access for both route parameters (`paramMap`) and query parameters (`queryParamMap`).
|
|
|
|
<table>
|
|
<tr>
|
|
<th>
|
|
Member
|
|
</th>
|
|
|
|
<th>
|
|
Description
|
|
</th>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>has(name)</code>
|
|
</td>
|
|
<td>
|
|
|
|
Returns `true` if the parameter name is in the map of parameters.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>get(name)</code>
|
|
</td>
|
|
<td>
|
|
|
|
Returns the parameter name value (a `string`) if present, or `null` if the parameter name is not in the map. Returns the _first_ element if the parameter value is actually an array of values.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>getAll(name)</code>
|
|
</td>
|
|
<td>
|
|
|
|
Returns a `string array` of the parameter name value if found, or an empty `array` if the parameter name value is not in the map. Use `getAll` when a single parameter could have multiple values.
|
|
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<td>
|
|
<code>keys</code>
|
|
</td>
|
|
<td>
|
|
|
|
Returns a `string array` of all parameter names in the map.
|
|
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
{@a reuse}
|
|
|
|
#### Observable <i>paramMap</i> 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.
|
|
|
|
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 `paramMap` property handles that beautifully.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a snapshot}
|
|
|
|
|
|
#### _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 parameter map.
|
|
You can access the parameters directly without subscribing or adding observable operators.
|
|
It's much simpler to write and read:
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-detail.component.2.ts" linenums="false" title="src/app/heroes/hero-detail.component.ts (ngOnInit snapshot)" region="snapshot">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
**Remember:** you only get the _initial_ value of the parameter map with this technique.
|
|
Stick with the observable `paramMap` approach if there's even a chance that the router
|
|
could re-use the component.
|
|
This sample stays with the observable `paramMap` strategy just in case.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a nav-to-list}
|
|
|
|
|
|
### Navigating back to the list component
|
|
|
|
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`_:
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-detail.component.1.ts" linenums="false" title="src/app/heroes/hero-detail.component.ts (excerpt)" region="gotoHeroes">
|
|
|
|
</code-example>
|
|
|
|
|
|
{@a optional-route-parameters}
|
|
|
|
### Route Parameters: Required or optional?
|
|
|
|
Use [*route parameters*](#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:
|
|
|
|
|
|
<code-example format="nocode">
|
|
localhost:3000/hero/15
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<figure>
|
|
<img src='generated/images/guide/router/selected-hero.png' alt="Selected hero">
|
|
</figure>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
{@a optionally-selecting}
|
|
|
|
|
|
### 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_](#link-parameters-array).
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-list.component.1.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (link-parameters-array)" region="link-parameters-array">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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`:
|
|
|
|
|
|
<code-example path="router/src/app/heroes/heroes-routing.module.ts" linenums="false" title="src/app/heroes/heroes-routing.module.ts (hero-detail-route)" region="hero-detail-route">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
When the user clicks the back button, the `HeroDetailComponent` constructs another _link parameters array_
|
|
which it uses to navigate back to the `HeroListComponent`.
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-detail.component.1.ts" linenums="false" title="src/app/heroes/hero-detail.component.ts (gotoHeroes)" region="gotoHeroes">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-detail.component.ts" linenums="false" title="src/app/heroes/hero-detail.component.ts (go to heroes)" region="gotoHeroes">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
<code-example language="bash">
|
|
localhost:3000/heroes;id=15;foo=foo
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
*Matrix URL* notation is an idea first introduced
|
|
in a [1996 proposal](http://www.w3.org/DesignIssues/MatrixURIs.html) 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.
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a route-parameters-activated-route}
|
|
|
|
|
|
### Route parameters in the *ActivatedRoute* service
|
|
|
|
The list of heroes is unchanged. No hero row is highlighted.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
The <live-example></live-example> *does* highlight the selected
|
|
row because it demonstrates the final state of the application which includes the steps you're *about* to cover.
|
|
At the moment this guide is describing the state of affairs *prior* to those steps.
|
|
|
|
</div>
|
|
|
|
|
|
|
|
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 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 you extend the router import statement to include the `ActivatedRoute` service symbol:
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-list.component.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (import)" region="import-router">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Import the `switchMap` operator to perform an operation on the `Observable` of route parameter map.
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-list.component.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (rxjs imports)" region="rxjs-imports">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Then you inject the `ActivatedRoute` in the `HeroListComponent` constructor.
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-list.component.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (constructor and ngOnInit)" region="ctor">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
Add an `isSelected` method that returns `true` when a hero's `id` matches the selected `id`.
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-list.component.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (isSelected)" region="isSelected">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Finally, update the template with a [class binding](guide/template-syntax#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:
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-list.component.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (template)" region="template">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
When the user navigates from the heroes list to the "Magneta" hero and back, "Magneta" appears selected:
|
|
|
|
<figure>
|
|
<img src='generated/images/guide/router/selected-hero.png' alt="Selected List">
|
|
</figure>
|
|
|
|
|
|
|
|
The optional `foo` route parameter is harmless and continues to be ignored.
|
|
|
|
|
|
{@a route-animation}
|
|
|
|
|
|
### Adding animations to the routed component
|
|
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](guide/animations)
|
|
to the `HeroDetailComponent`.
|
|
|
|
First import `BrowserAnimationsModule`:
|
|
|
|
<code-example path="router/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts (animations-module)" region="animations-module">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Create an `animations.ts` file in the root `src/app/` folder. The contents look like this:
|
|
|
|
<code-example path="router/src/app/animations.ts" linenums="false" title="src/app/animations.ts (excerpt)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
<code-example path="router/src/app/heroes/hero-detail.component.ts" linenums="false" title="src/app/heroes/hero-detail.component.ts (host bindings)" region="host-bindings">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
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_.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a milestone-3-wrap-up}
|
|
|
|
|
|
### Milestone 3 wrap up
|
|
|
|
You've learned how to do the following:
|
|
|
|
* Organize the app into *feature areas*.
|
|
* Navigate imperatively from one component to another.
|
|
* 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:
|
|
|
|
|
|
<div class='filetree'>
|
|
|
|
<div class='file'>
|
|
router-sample
|
|
</div>
|
|
|
|
<div class='children'>
|
|
|
|
<div class='file'>
|
|
src
|
|
</div>
|
|
|
|
<div class='children'>
|
|
|
|
<div class='file'>
|
|
app
|
|
</div>
|
|
|
|
<div class='children'>
|
|
|
|
<div class='file'>
|
|
heroes
|
|
</div>
|
|
|
|
<div class='children'>
|
|
|
|
<div class='file'>
|
|
hero-detail.component.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
hero-list.component.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
hero.service.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
heroes.module.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
heroes-routing.module.ts
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class='file'>
|
|
app.component.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
app.module.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
app-routing.module.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
crisis-list.component.ts
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class='file'>
|
|
main.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
index.html
|
|
</div>
|
|
|
|
<div class='file'>
|
|
styles.css
|
|
</div>
|
|
|
|
<div class='file'>
|
|
tsconfig.json
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class='file'>
|
|
node_modules ...
|
|
</div>
|
|
|
|
<div class='file'>
|
|
package.json
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
Here are the relevant files for this version of the sample application.
|
|
|
|
<code-tabs>
|
|
|
|
<code-pane title="app.component.ts" path="router/src/app/app.component.1.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="app.module.ts" path="router/src/app/app.module.3.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="app-routing.module.ts" path="router/src/app/app-routing.module.2.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="hero-list.component.ts" path="router/src/app/heroes/hero-list.component.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="hero-detail.component.ts" path="router/src/app/heroes/hero-detail.component.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="hero.service.ts" path="router/src/app/heroes/hero.service.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="heroes.module.ts" path="router/src/app/heroes/heroes.module.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="heroes-routing.module.ts" path="router/src/app/heroes/heroes-routing.module.ts">
|
|
|
|
</code-pane>
|
|
|
|
</code-tabs>
|
|
|
|
|
|
|
|
{@a milestone-4}
|
|
|
|
|
|
|
|
## 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:
|
|
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis.service.ts" linenums="false" title="src/app/crisis-center/crisis.service.ts (mock-crises)" region="mock-crises">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<div class="alert is-helpful">
|
|
|
|
|
|
|
|
In keeping with the
|
|
<a href="https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html" title="Separation of Concerns">*Separation of Concerns* principle</a>,
|
|
changes to the *Crisis Center* won't affect the `AppModule` or
|
|
any other feature's component.
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a crisis-child-routes}
|
|
|
|
|
|
### 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:
|
|
|
|
|
|
<figure>
|
|
<img src='generated/images/guide/router/component-tree.png' alt="Component Tree">
|
|
</figure>
|
|
|
|
|
|
|
|
{@a child-routing-component}
|
|
|
|
|
|
### Child routing component
|
|
|
|
Add the following `crisis-center.component.ts` to the `crisis-center` folder:
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis-center.component.ts" linenums="false" title="src/app/crisis-center/crisis-center.component.ts">
|
|
</code-example>
|
|
|
|
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.
|
|
|
|
|
|
{@a child-route-config}
|
|
|
|
|
|
### Child route configuration
|
|
|
|
As a host page for the "Crisis Center" feature, add the following `crisis-center-home.component.ts` to the `crisis-center` folder.
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis-center-home.component.ts" linenums="false" title="src/app/crisis-center/crisis-center-home.component.ts" >
|
|
</code-example>
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis-center-routing.module.1.ts" linenums="false" title="src/app/crisis-center/crisis-center-routing.module.ts (Routes)" region="routes">
|
|
</code-example>
|
|
|
|
|
|
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](#reuse)
|
|
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
|
|
|
|
<code-example>
|
|
localhost:3000/crisis-center/2
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Here's the complete `crisis-center-routing.module.ts` file with its imports.
|
|
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis-center-routing.module.1.ts" linenums="false" title="src/app/crisis-center/crisis-center-routing.module.ts (excerpt)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
{@a import-crisis-module}
|
|
|
|
|
|
### 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`:
|
|
|
|
|
|
<code-example path="router/src/app/app.module.4.ts" linenums="false" title="src/app/app.module.ts (import CrisisCenterModule)" region="crisis-center-module">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/app-routing.module.3.ts" linenums="false" title="src/app/app-routing.module.ts (v3)" region="v3">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
|
|
{@a relative-navigation}
|
|
|
|
|
|
### 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.
|
|
|
|
Here's an example:
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
**Always** specify the complete _absolute_ path when calling router's `navigateByUrl` method.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a nav-to-crisis}
|
|
|
|
|
|
### Navigate to crisis detail with a relative URL
|
|
|
|
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.
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis-list.component.ts" linenums="false" title="src/app/crisis-center/crisis-list.component.ts (constructor)" region="ctor">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis-list.component.ts" linenums="false" title="src/app/crisis-center/crisis-list.component.ts (relative navigation)" region="onSelect">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis-list.component.1.ts" linenums="false" title="src/app/crisis-center/crisis-list.component.ts (relative routerLink)" region="relative-navigation-router-link">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Update the `gotoCrises` method of the `CrisisDetailComponent` to navigate back to the *Crisis Center* list using relative path navigation.
|
|
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis-detail.component.ts" linenums="false" title="src/app/crisis-center/crisis-detail.component.ts (relative navigation)" region="gotoCrises-navigate">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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`.
|
|
|
|
|
|
{@a named-outlets}
|
|
|
|
|
|
|
|
### 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 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.
|
|
|
|
|
|
<code-example path="router/src/app/app.component.4.ts" linenums="false" title="src/app/app.component.ts (outlets)" region="outlets">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
That's where a popup will go, once you learn how to route a popup component to it.
|
|
|
|
|
|
{@a secondary-routes}
|
|
|
|
|
|
#### 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.
|
|
|
|
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".
|
|
|
|
|
|
<figure>
|
|
<img src='generated/images/guide/router/contact-popup.png' alt="Contact popup">
|
|
</figure>
|
|
|
|
|
|
|
|
Here's the component and its template:
|
|
|
|
|
|
<code-tabs>
|
|
|
|
<code-pane title="src/app/compose-message.component.ts" path="router/src/app/compose-message.component.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="src/app/compose-message.component.html" path="router/src/app/compose-message.component.html">
|
|
|
|
</code-pane>
|
|
|
|
</code-tabs>
|
|
|
|
|
|
|
|
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](#clear-secondary-routes).
|
|
|
|
As with other application components, you add the `ComposeMessageComponent` to the `declarations` of an `NgModule`.
|
|
Do so in the `AppModule`.
|
|
|
|
|
|
{@a add-secondary-route}
|
|
|
|
|
|
#### Add a secondary route
|
|
|
|
Open the `AppRoutingModule` and add a new `compose` route to the `appRoutes`.
|
|
|
|
<code-example path="router/src/app/app-routing.module.3.ts" linenums="false" title="src/app/app-routing.module.ts (compose route)" region="compose">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
<code-example path="router/src/app/app.component.4.ts" linenums="false" title="src/app/app.component.ts (contact-link)" region="contact-link">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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_.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a secondary-route-navigation}
|
|
|
|
|
|
#### 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.
|
|
|
|
|
|
<code-example>
|
|
http://.../crisis-center(popup:compose)
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<code-example>
|
|
http://.../heroes(popup:compose)
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
{@a clear-secondary-routes}
|
|
|
|
|
|
#### Clearing secondary routes
|
|
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:
|
|
|
|
<code-example path="router/src/app/compose-message.component.ts" linenums="false" title="src/app/compose-message.component.ts (closePopup)" region="closePopup">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
It navigates imperatively with the `Router.navigate()` method, passing in a [link parameters array](#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.
|
|
|
|
{@a guards}
|
|
|
|
## 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.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
The guard can also tell the router to navigate elsewhere, effectively canceling the current navigation.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
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 guard interfaces:
|
|
|
|
* [`CanActivate`](api/router/CanActivate) to mediate navigation *to* a route.
|
|
|
|
* [`CanActivateChild`](api/router/CanActivateChild) to mediate navigation *to* a child route.
|
|
|
|
* [`CanDeactivate`](api/router/CanDeactivate) to mediate navigation *away* from the current route.
|
|
|
|
* [`Resolve`](api/router/Resolve) to perform route data retrieval *before* route activation.
|
|
|
|
* [`CanLoad`](api/router/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.
|
|
|
|
|
|
{@a can-activate-guard}
|
|
|
|
|
|
### _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
|
|
|
|
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:
|
|
|
|
|
|
<div class='filetree'>
|
|
|
|
<div class='file'>
|
|
src/app/admin
|
|
</div>
|
|
|
|
<div class='children'>
|
|
|
|
<div class='file'>
|
|
admin-dashboard.component.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
admin.component.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
admin.module.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
admin-routing.module.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
manage-crises.component.ts
|
|
</div>
|
|
|
|
<div class='file'>
|
|
manage-heroes.component.ts
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<code-tabs>
|
|
|
|
<code-pane title="src/app/admin/admin-dashboard.component.ts" path="router/src/app/admin/admin-dashboard.component.1.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="src/app/admin/admin.component.ts" path="router/src/app/admin/admin.component.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="src/app/admin/admin.module.ts" path="router/src/app/admin/admin.module.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="src/app/admin/manage-crises.component.ts" path="router/src/app/admin/manage-crises.component.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="src/app/admin/manage-heroes.component.ts" path="router/src/app/admin/manage-heroes.component.ts">
|
|
|
|
</code-pane>
|
|
|
|
</code-tabs>
|
|
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
The initial admin routing configuration:
|
|
|
|
|
|
<code-example path="router/src/app/admin/admin-routing.module.1.ts" linenums="false" title="src/app/admin/admin-routing.module.ts (admin routing)" region="admin-routes">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
{@a component-less-route}
|
|
|
|
|
|
### Component-less route: grouping routes without a component
|
|
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](#can-activate-child-guard).
|
|
|
|
|
|
Next, import the `AdminModule` into `app.module.ts` and add it to the `imports` array
|
|
to register the admin routes.
|
|
|
|
|
|
<code-example path="router/src/app/app.module.4.ts" linenums="false" title="src/app/app.module.ts (admin module)" region="admin-module">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Add an "Admin" link to the `AppComponent` shell so that users can get to this feature.
|
|
|
|
|
|
<code-example path="router/src/app/app.component.5.ts" linenums="false" title="src/app/app.component.ts (template)" region="template">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
{@a guard-admin-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.
|
|
|
|
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 method 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:
|
|
|
|
|
|
<code-example path="router/src/app/auth-guard.service.1.ts" linenums="false" title="src/app/auth-guard.service.ts (excerpt)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Next, open `admin-routing.module.ts `, import the `AuthGuard` class, and
|
|
update the admin route with a `canActivate` guard property that references it:
|
|
|
|
|
|
<code-example path="router/src/app/admin/admin-routing.module.2.ts" linenums="false" title="src/app/admin/admin-routing.module.ts (guarded admin route)" region="admin-route">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
The admin feature is now protected by the guard, albeit protected poorly.
|
|
|
|
|
|
{@a teach-auth}
|
|
|
|
|
|
#### Teach *AuthGuard* to authenticate
|
|
|
|
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`:
|
|
|
|
|
|
<code-example path="router/src/app/auth.service.ts" linenums="false" title="src/app/auth.service.ts (excerpt)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/auth-guard.service.2.ts" linenums="false" title="src/app/auth-guard.service.ts (v2)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
{@a add-login-component}
|
|
|
|
|
|
#### 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.
|
|
|
|
|
|
<code-tabs>
|
|
|
|
<code-pane title="src/app/app.module.ts" path="router/src/app/app.module.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="src/app/login.component.ts" path="router/src/app/login.component.1.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="src/app/login-routing.module.ts" path="router/src/app/login-routing.module.ts">
|
|
|
|
</code-pane>
|
|
|
|
</code-tabs>
|
|
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
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](#asynchronous-routing).
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{@a can-activate-child-guard}
|
|
|
|
|
|
### _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.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`:
|
|
|
|
|
|
<code-example path="router/src/app/auth-guard.service.3.ts" linenums="false" title="src/app/auth-guard.service.ts (excerpt)" region="can-activate-child">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/admin/admin-routing.module.3.ts" linenums="false" title="src/app/admin/admin-routing.module.ts (excerpt)" region="can-activate-child">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
{@a can-deactivate-guard}
|
|
|
|
|
|
### _CanDeactivate_: handling unsaved changes
|
|
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.
|
|
|
|
{@a cancel-save}
|
|
|
|
|
|
### Cancel and save
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis-detail.component.ts" linenums="false" title="src/app/crisis-center/crisis-detail.component.ts (cancel and save methods)" region="cancel-save">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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*.
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
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.
|
|
|
|
</div>
|
|
|
|
|
|
|
|
The `DialogService`, provided in the `AppModule` for app-wide use, does the asking.
|
|
|
|
It returns a [promise](http://exploringjs.com/es6/ch_promises.html) 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`).
|
|
|
|
|
|
{@a CanDeactivate}
|
|
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/can-deactivate-guard.service.ts" title="src/app/can-deactivate-guard.service.ts">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/can-deactivate-guard.service.1.ts" linenums="false" title="src/app/can-deactivate-guard.service.ts (component-specific)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Looking back at the `CrisisDetailComponent`, it implements the confirmation workflow for unsaved changes.
|
|
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis-detail.component.ts" linenums="false" title="src/app/crisis-center/crisis-detail.component.ts (excerpt)" region="canDeactivate">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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 property.
|
|
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis-center-routing.module.3.ts" linenums="false" title="src/app/crisis-center/crisis-center-routing.module.ts (can deactivate guard)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Add the `Guard` to the main `AppRoutingModule` `providers` array so the
|
|
`Router` can inject it during the navigation process.
|
|
|
|
|
|
<code-example path="router/src/app/app-routing.module.4.ts">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Now you have given the user a safeguard against unsaved changes.
|
|
{@a Resolve}
|
|
|
|
{@a resolve-guard}
|
|
|
|
|
|
### _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.
|
|
|
|
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*.
|
|
|
|
|
|
{@a fetch-before-navigating}
|
|
|
|
|
|
### Fetch data before navigating
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis-detail-resolver.service.ts" title="src/app/crisis-center/crisis-detail-resolver.service.ts">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis-center-routing.module.4.ts" linenums="false" title="src/app/crisis-center/crisis-center-routing.module.ts (resolver)" region="crisis-detail-resolver">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/crisis-center/crisis-detail.component.ts" linenums="false" title="src/app/crisis-center/crisis-detail.component.ts (ngOnInit v2)" region="ngOnInit">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
**Two critical points**
|
|
|
|
1. 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.
|
|
|
|
1. 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.
|
|
|
|
|
|
<code-tabs>
|
|
|
|
<code-pane title="app.component.ts" path="router/src/app/app.component.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="crisis-center-home.component.ts" path="router/src/app/crisis-center/crisis-center-home.component.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="crisis-center.component.ts" path="router/src/app/crisis-center/crisis-center.component.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="crisis-center-routing.module.ts" path="router/src/app/crisis-center/crisis-center-routing.module.4.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="crisis-list.component.ts" path="router/src/app/crisis-center/crisis-list.component.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="crisis-detail.component.ts" path="router/src/app/crisis-center/crisis-detail.component.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="crisis-detail-resolver.service.ts" path="router/src/app/crisis-center/crisis-detail-resolver.service.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="crisis.service.ts" path="router/src/app/crisis-center/crisis.service.ts">
|
|
|
|
</code-pane>
|
|
|
|
</code-tabs>
|
|
|
|
|
|
|
|
<code-tabs>
|
|
|
|
<code-pane title="auth-guard.service.ts" path="router/src/app/auth-guard.service.3.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="can-deactivate-guard.service.ts" path="router/src/app/can-deactivate-guard.service.ts">
|
|
|
|
</code-pane>
|
|
|
|
</code-tabs>
|
|
|
|
|
|
|
|
{@a query-parameters}
|
|
|
|
|
|
{@a fragment}
|
|
|
|
|
|
### Query parameters and fragments
|
|
|
|
In the [route parameters](#optional-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](https://en.wikipedia.org/wiki/Fragment_identifier) 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.
|
|
|
|
|
|
<code-example path="router/src/app/auth-guard.service.4.ts" linenums="false" title="src/app/auth-guard.service.ts (v3)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/login.component.ts" linenums="false" title="src/app/login.component.ts (preserve)" region="preserve">
|
|
|
|
</code-example>
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
The `queryParamsHandling` feature also provides a `merge` option, which will preserve and combine the current query parameters with any provided query parameters
|
|
when navigating.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
Since you'll be navigating to the *Admin Dashboard* route after logging in, you'll update it to handle the
|
|
query parameters and fragment.
|
|
|
|
|
|
<code-example path="router/src/app/admin/admin-dashboard.component.2.ts" linenums="false" title="src/app/admin/admin-dashboard.component.ts (v2)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
*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.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
The `query params` and `fragment` can also be preserved using a `RouterLink` with
|
|
the `queryParamsHandling` and `preserveFragment` bindings respectively.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{@a asynchronous-routing}
|
|
|
|
## 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.
|
|
|
|
|
|
{@a lazy-loading-route-config}
|
|
|
|
|
|
### 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`.
|
|
|
|
|
|
<code-example path="router/src/app/app-routing.module.5.ts" region="admin-1" title="app-routing.module.ts (load children)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
{@a can-load-guard}
|
|
|
|
|
|
### _CanLoad_ Guard: 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.service.ts`.
|
|
Import the `CanLoad` interface from `@angular/router`.
|
|
Add it to the `AuthGuard` class's `implements` list.
|
|
Then implement `canLoad()` as follows:
|
|
|
|
|
|
<code-example path="router/src/app/auth-guard.service.ts" linenums="false" title="src/app/auth-guard.service.ts (CanLoad guard)" region="canLoad">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
<code-example path="router/src/app/app-routing.module.5.ts" region="admin" title="app-routing.module.ts (lazy admin route)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
{@a preloading}
|
|
|
|
|
|
### Preloading: background loading of feature areas
|
|
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_.
|
|
|
|
|
|
{@a how-preloading}
|
|
|
|
|
|
#### 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 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](#custom-preloading) 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.
|
|
|
|
|
|
{@a lazy-load-crisis-center}
|
|
|
|
|
|
#### 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 load.
|
|
|
|
1. Change the `crisis-center` path in the `CrisisCenterRoutingModule` to an empty string.
|
|
|
|
1. Add a `crisis-center` route to the `AppRoutingModule`.
|
|
|
|
1. Set the `loadChildren` string to load the `CrisisCenterModule`.
|
|
|
|
1. Remove all mention of the `CrisisCenterModule` from `app.module.ts`.
|
|
|
|
Here are the updated modules _before enabling preload_:
|
|
|
|
|
|
<code-tabs>
|
|
|
|
<code-pane title="app.module.ts" path="router/src/app/app.module.ts">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="app-routing.module.ts" path="router/src/app/app-routing.module.6.ts" region="preload-v1">
|
|
|
|
</code-pane>
|
|
|
|
<code-pane title="crisis-center-routing.module.ts" path="router/src/app/crisis-center/crisis-center-routing.module.ts">
|
|
|
|
</code-pane>
|
|
|
|
</code-tabs>
|
|
|
|
|
|
|
|
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:
|
|
|
|
<code-example path="router/src/app/app-routing.module.6.ts" linenums="false" title="src/app/app-routing.module.ts (preload all)" region="forRoot">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
{@a preload-canload}
|
|
|
|
|
|
#### CanLoad blocks preload
|
|
|
|
The `PreloadAllModules` strategy does not load feature areas protected by a [CanLoad](#can-load-guard) 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 method and rely on the [canActivate()](#can-activate-guard) guard alone.
|
|
|
|
|
|
{@a custom-preloading}
|
|
|
|
|
|
### Custom Preloading Strategy
|
|
|
|
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`.
|
|
|
|
|
|
<code-example path="router/src/app/app-routing.module.ts" linenums="false" title="src/app/app-routing.module.ts (route data preload)" region="preload-v2">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
Add a new file to the project called `selective-preloading-strategy.ts`
|
|
and define a `SelectivePreloadingStrategy` service class as follows:
|
|
|
|
<code-example path="router/src/app/selective-preloading-strategy.ts" linenums="false" title="src/app/selective-preloading-strategy.ts (excerpt)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
`SelectivePreloadingStrategy` implements the `PreloadingStrategy`, which has one method, `preload`.
|
|
|
|
The router calls the `preload` method with two arguments:
|
|
|
|
1. The route to consider.
|
|
1. 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`.
|
|
|
|
1. Import `SelectivePreloadingStrategy` into `AppRoutingModule`.
|
|
1. Replace the `PreloadAllModules` strategy in the call to `forRoot` with this `SelectivePreloadingStrategy`.
|
|
1. 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.
|
|
|
|
1. Import the `SelectivePreloadingStrategy` (it's a service).
|
|
1. Inject it into the dashboard's constructor.
|
|
1. Update the template to display the strategy service's `preloadedModules` array.
|
|
|
|
When you're done it looks like this.
|
|
|
|
|
|
<code-example path="router/src/app/admin/admin-dashboard.component.ts" linenums="false" title="src/app/admin/admin-dashboard.component.ts (preloaded modules)">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
{@a inspect-config}
|
|
|
|
|
|
|
|
## Inspect the router's configuration
|
|
|
|
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](#routing-module-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.
|
|
|
|
<code-example path="router/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts (inspect the router config)" region="inspect-config">
|
|
|
|
</code-example>
|
|
|
|
|
|
{@a final-app}
|
|
|
|
|
|
## Wrap up and final app
|
|
|
|
You've covered a lot of ground in this guide and the application is too big to reprint here.
|
|
Please visit the <live-example title="Router Sample in Plunker"></live-example>
|
|
where you can download the final source code.
|
|
|
|
|
|
{@a appendices}
|
|
|
|
|
|
|
|
## Appendices
|
|
|
|
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}
|
|
|
|
|
|
|
|
### Appendix: link parameters array
|
|
|
|
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:
|
|
|
|
|
|
<code-example path="router/src/app/app.component.3.ts" linenums="false" title="src/app/app.component.ts (h-anchor)" region="h-anchor">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
You've written a two element array when specifying a route parameter like this:
|
|
|
|
|
|
<code-example path="router/src/app/heroes/hero-list.component.1.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (nav-to-detail)" region="nav-to-detail">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
You can provide optional route parameters in an object like this:
|
|
|
|
|
|
<code-example path="router/src/app/app.component.3.ts" linenums="false" title="src/app/app.component.ts (cc-query-params)" region="cc-query-params">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
<code-example path="router/src/app/app.component.3.ts" linenums="false" title="src/app/app.component.ts (cc-anchor-w-default)" region="cc-anchor-w-default">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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*:
|
|
|
|
|
|
<code-example path="router/src/app/app.component.3.ts" linenums="false" title="src/app/app.component.ts (Dragon-anchor)" region="Dragon-anchor">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
* 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.
|
|
* 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:
|
|
|
|
|
|
<code-example path="router/src/app/app.component.3.ts" linenums="false" title="src/app/app.component.ts (template)" region="template">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
{@a browser-url-styles}
|
|
|
|
|
|
{@a location-strategy}
|
|
|
|
|
|
|
|
### Appendix: *LocationStrategy* and browser URL styles
|
|
|
|
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
|
|
<a href="https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries" title="HTML5 browser history push-state">history.pushState</a>,
|
|
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:
|
|
|
|
|
|
<code-example format="nocode">
|
|
localhost:3002/crisis-center/
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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*.
|
|
|
|
|
|
<code-example format="nocode">
|
|
localhost:3002/src/#/crisis-center/
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
The router supports both styles with two `LocationStrategy` providers:
|
|
|
|
1. `PathLocationStrategy`—the default "HTML5 pushState" style.
|
|
1. `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.
|
|
|
|
|
|
<div class="l-sub-section">
|
|
|
|
|
|
|
|
Learn about providers and the bootstrap process in the
|
|
[Dependency Injection guide](guide/dependency-injection#bootstrap).
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
#### 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
|
|
<a href="https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries" title="Browser history push-state">HTML5 pushState</a>
|
|
style by default, you *must* configure that strategy with a **base href**.
|
|
|
|
The preferred way to configure the strategy is to add a
|
|
<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base" title="base href"><base href> element</a>
|
|
tag in the `<head>` of the `index.html`.
|
|
|
|
|
|
<code-example path="router/src/index.html" linenums="false" title="src/index.html (base-href)" region="base-href">
|
|
|
|
</code-example>
|
|
|
|
|
|
|
|
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:
|
|
|
|
1. Provide the router with an appropriate [APP_BASE_HREF][] value.
|
|
1. Use _root URLs_ for all web resources: CSS, images, scripts, and template HTML files.
|
|
|
|
{@a hashlocationstrategy}
|
|
|
|
#### *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`.
|
|
|
|
|
|
<code-example path="router/src/app/app.module.6.ts" linenums="false" title="src/app/app.module.ts (hash URL strategy)">
|
|
|
|
</code-example>
|