2016-02-06 02:27:06 -05:00
include ../_util-fns
2015-12-23 12:42:57 -05:00
:marked
# Routing Around the App
2016-06-19 00:20:38 -04:00
We received new requirements for our Tour of Heroes application:
2016-06-10 12:37:33 -04:00
* Add a *Dashboard* view.
* Navigate between the *Heroes* and *Dashboard* views.
* Clicking on a hero in either view navigates to a detail view of the selected hero.
2016-06-19 00:20:38 -04:00
* Clicking a *deep link* in an email opens the detail view for a particular hero;
2015-12-23 12:42:57 -05:00
When we’ re done, users will be able to navigate the app like this:
figure.image-display
img(src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations")
:marked
We'll add Angular’ s *Component Router* to our app to satisfy these requirements.
.l-sub-section
:marked
2016-06-19 00:20:38 -04:00
The [Routing and Navigation](../guide/router.html) chapter covers the router in more detail
than we will in this tutorial.
2016-06-07 18:51:25 -04:00
p Run the #[+liveExampleLink2('', 'toh-5')] for this part.
2015-12-23 12:42:57 -05:00
.l-sub-section
2016-02-28 16:55:37 -05:00
img(src='/resources/images/devguide/plunker-separate-window-button.png' alt="pop out the window" align="right" style="margin-right:-20px")
2015-12-23 12:42:57 -05:00
:marked
2016-06-19 00:20:38 -04:00
To see the URL changes in the browser address bar,
2016-02-28 16:55:37 -05:00
pop out the preview window by clicking the blue 'X' button in the upper right corner:
2015-12-23 12:42:57 -05:00
.l-main-section
:marked
## Where We Left Off
2016-06-19 00:20:38 -04:00
Before we continue with our Tour of Heroes, let’ s verify that we have the following structure after adding our hero service
2015-12-23 12:42:57 -05:00
and hero detail component. If not, we’ ll need to go back and follow the previous chapters.
.filetree
.file angular2-tour-of-heroes
.children
.file app
.children
.file app.component.ts
.file hero.ts
.file hero-detail.component.ts
.file hero.service.ts
.file main.ts
.file mock-heroes.ts
2016-02-11 18:08:06 -05:00
.file node_modules ...
2016-06-19 00:20:38 -04:00
.file typings ...
2015-12-23 12:42:57 -05:00
.file index.html
.file package.json
2016-04-27 14:28:22 -04:00
.file styles.css
.file systemjs.config.js
2015-12-23 12:42:57 -05:00
.file tsconfig.json
2016-02-11 18:08:06 -05:00
.file typings.json
2015-12-23 12:42:57 -05:00
:marked
### Keep the app transpiling and running
Open a terminal/console window and enter the following command to
start the TypeScript compiler, start the server, and watch for changes:
2016-06-07 18:51:25 -04:00
code-example(language="bash").
2015-12-23 12:42:57 -05:00
npm start
:marked
The application runs and updates automatically as we continue to build the Tour of Heroes.
## Action plan
2016-06-15 10:46:26 -04:00
Here's our plan:
2016-06-19 00:20:38 -04:00
2016-06-15 10:46:26 -04:00
* Turn `AppComponent` into an application shell that only handles navigation
* Relocate the *Heroes* concerns within the current `AppComponent` to a separate `HeroesComponent`
* Add routing
* Create a new `DashboardComponent`
* Tie the *Dashboard* into the navigation structure
2015-12-23 12:42:57 -05:00
.l-sub-section
:marked
*Routing* is another name for *navigation*. The *router* is the mechanism for navigating from view to view.
2016-06-19 00:20:38 -04:00
.l-main-section
2015-12-23 12:42:57 -05:00
:marked
## Splitting the *AppComponent*
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
Our current app loads `AppComponent` and immediately displays the list of heroes.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
Our revised app should present a shell with a choice of views (*Dashboard* and *Heroes*) and then default to one of them.
The `AppComponent` should only handle navigation.
Let's move the display of *Heroes* out of `AppComponent` and into its own `HeroesComponent`.
### *HeroesComponent*
`AppComponent` is already dedicated to *Heroes*.
Instead of moving anything out of `AppComponent`, we'll just rename it `HeroesComponent`
and create a new `AppComponent` shell separately.
2016-06-15 10:46:26 -04:00
The steps are to rename:
* `app.component.ts` file to `heroes.component.ts`
* `AppComponent` class to `HeroesComponent`
* Selector `my-app` to `my-heroes`
2015-12-23 12:42:57 -05:00
:marked
2016-06-10 12:37:33 -04:00
+makeExample('toh-5/ts/app/heroes.component.ts', 'heroes-component-renaming', 'app/heroes.component.ts (showing renamings only)')(format=".")
2015-12-23 12:42:57 -05:00
:marked
## Create *AppComponent*
2016-06-19 00:20:38 -04:00
The new `AppComponent` will be the application shell.
2015-12-23 12:42:57 -05:00
It will have some navigation links at the top and a display area below for the pages we navigate to.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
The initial steps are:
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
* create a new file named `app.component.ts`.
* define an `AppComponent` class.
* `export` it so we can reference it during bootstrapping in `main.ts`.
* expose an application `title` property.
* add the `@Component` metadata decorator above the class with a `my-app` selector.
* add a template with `<h1>` tags surrounding a binding to the `title` property.
* add the `<my-heroes>` tags to the template so we still see the heroes.
* add the `HeroesComponent` to the `directives` array so Angular recognizes the `<my-heroes>` tags.
* add the `HeroService` to the `providers` array because we'll need it in every other view.
* add the supporting `import` statements.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
Our first draft looks like this:
+makeExample('toh-5/ts/app/app.component.1.ts', null, 'app/app.component.ts (v1)')
:marked
.callout.is-critical
header Remove <i>HeroService</i> from the <i>HeroesComponent</i> providers
:marked
Go back to the `HeroesComponent` and **remove the `HeroService`** from its `providers` array.
We are *promoting* this service from the `HeroesComponent` to the `AppComponent`.
We ***do not want two copies*** of this service at two different levels of our app.
:marked
2016-06-19 00:20:38 -04:00
The app still runs and still displays heroes.
2015-12-23 12:42:57 -05:00
Our refactoring of `AppComponent` into a new `AppComponent` and a `HeroesComponent` worked!
We have done no harm.
:marked
## Add Routing
2016-06-19 00:20:38 -04:00
We're ready to take the next step.
2015-12-23 12:42:57 -05:00
Instead of displaying heroes automatically, we'd like to show them *after* the user clicks a button.
In other words, we'd like to navigate to the list of heroes.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
We'll need the Angular *Component Router*.
2016-06-19 00:20:38 -04:00
2016-04-27 14:28:22 -04:00
### Set the base tag
2016-06-19 00:20:38 -04:00
Open the `index.html` and add `<base href="/">` at the top of the `<head>` section.
2015-12-23 12:42:57 -05:00
+makeExample('toh-5/ts/index.html', 'base-href', 'index.html (base href)')(format=".")
.callout.is-important
header base href is essential
:marked
2016-06-19 00:20:38 -04:00
See the *base href* section of the [Router](../guide/router.html#!#base-href) chapter to learn why this matters.
2015-12-23 12:42:57 -05:00
:marked
2016-06-19 00:20:38 -04:00
The Angular router is a combination of multiple provided services (`provideRouter`), multiple directives (`ROUTER_DIRECTIVES`),
and a configuration (`RouterConfig`). We'll configure our routes first:
### Configure and add the router
Our application doesn't have a router yet. We'll create a configuration file for our routes that
does two things
(a) configure that router with *routes*. (b) provide an export to add the router to our bootstrap
*Routes* tell the router which views to display when a user clicks a link or
2015-12-23 12:42:57 -05:00
pastes a URL into the browser address bar.
Let's define our first route, a route to the `HeroesComponent`.
2016-06-19 00:20:38 -04:00
+makeExample('toh-5/ts/app/app.routes.2.ts', '', 'app/app.routes.ts')(format=".")
2015-12-23 12:42:57 -05:00
:marked
2016-06-19 00:20:38 -04:00
The `RouterConfig` is an array of *route definitions*.
We have only one route definition at the moment but rest assured, we'll add more.
This *route definition* has two parts:
2015-12-23 12:42:57 -05:00
* **path**: the router matches this route's path to the URL in the browser address bar (`/heroes`).
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
* **component**: the component that the router should create when navigating to this route (`HeroesComponent`).
.l-sub-section
:marked
2016-06-19 00:20:38 -04:00
Learn more about defining routes with RouterConfig in the [Routing](../guide/router.html) chapter.
:marked
### Make the router available.
The *Component Router* is a service. We have to import our `APP_ROUTER_PROVIDERS` which
contains our configured router and make it available to the application by adding it to
the `bootstrap` array.
+makeExample('toh-5/ts/app/main.ts', '', 'app/main.ts')(format=".")
2015-12-23 12:42:57 -05:00
:marked
### Router Outlet
2016-06-19 00:20:38 -04:00
If we paste the path, `/heroes`, into the browser address bar,
2015-12-23 12:42:57 -05:00
the router should match it to the `'Heroes'` route and display the `HeroesComponent`.
But where?
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
We have to ***tell it where*** by adding `<router-outlet>` marker tags to the bottom of the template.
`RouterOutlet` is one of the `ROUTER_DIRECTIVES`.
The router displays each component immediately below the `<router-outlet>` as we navigate through the application.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
### Router Links
We don't really expect users to paste a route URL into the address bar.
We add an anchor tag to the template which, when clicked, triggers navigation to the `HeroesComponent`.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
The revised template looks like this:
2016-06-10 12:37:33 -04:00
+makeExample('toh-5/ts/app/app.component.2.ts', 'template', 'app/app.component.ts (template v1)')(format=".")
2015-12-23 12:42:57 -05:00
:marked
2016-06-19 00:20:38 -04:00
Notice the `[routerLink]` binding in the anchor tag.
2015-12-23 12:42:57 -05:00
We bind the `RouterLink` directive (another of the `ROUTER_DIRECTIVES`) to an array
that tells the router where to navigate when the user clicks the link.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
We define a *routing instruction* with a *link parameters array*.
2016-06-19 00:20:38 -04:00
The array only has one element in our little sample, the quoted ***path* of the route** to follow.
Looking back at the route configuration, we confirm that `'/heroes'` is the path of the route to the `HeroesComponent`.
2015-12-23 12:42:57 -05:00
.l-sub-section
:marked
2016-06-19 00:20:38 -04:00
Learn about the *link parameters array* in the [Routing](../guide/router.html#link-parameters-array) chapter.
2015-12-23 12:42:57 -05:00
:marked
2016-06-19 00:20:38 -04:00
Refresh the browser. We see only the app title. We don't see the heroes list.
2015-12-23 12:42:57 -05:00
.l-sub-section
:marked
2016-06-19 00:20:38 -04:00
The browser's address bar shows `/`.
The route path to `HeroesComponent` is `/heroes`, not `/`.
2015-12-23 12:42:57 -05:00
We don't have a route that matches the path `/`, so there is nothing to show.
That's something we'll want to fix.
:marked
2016-06-19 00:20:38 -04:00
We click the "Heroes" navigation link, the browser bar updates to `/heroes`,
2015-12-23 12:42:57 -05:00
and now we see the list of heroes. We are navigating at last!
At this stage, our `AppComponent` looks like this.
+makeExample('toh-5/ts/app/app.component.2.ts',null, 'app/app.component.ts (v2)')
:marked
The *AppComponent* is now attached to a router and displaying routed views.
For this reason and to distinguish it from other kinds of components,
we call this type of component a *Router Component*.
:marked
## Add a *Dashboard*
Routing only makes sense when we have multiple views. We need another view.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
Create a placeholder `DashboardComponent` that gives us something to navigate to and from.
+makeExample('toh-5/ts/app/dashboard.component.1.ts',null, 'app/dashboard.component.ts (v1)')(format=".")
:marked
We’ ll come back and make it more useful later.
### Configure the dashboard route
2016-06-19 00:20:38 -04:00
Go back to `app.routes.ts` and teach it to navigate to the dashboard.
Import the `DashboardComponent` so we can reference it in the dashboard route definition.
Add the following `'Dashboard'` route definition to the `RouterConfig` array of definitions.
+makeExample('toh-5/ts/app/app.routes.1.ts','dashboard-route', 'app/app.routes.ts (Dashboard route)')(format=".")
2015-12-23 12:42:57 -05:00
.l-sub-section
:marked
2016-06-19 00:20:38 -04:00
**Redirect**
We want the app to show the dashboard when it starts and
2015-12-23 12:42:57 -05:00
we want to see a nice URL in the browser address bar that says `/dashboard`.
2016-06-19 00:20:38 -04:00
Remember that the browser launches with `/` in the address bar.
We can use a redirect route to make this happen.
+makeExample('toh-5/ts/app/app.routes.1.ts','redirect-route', 'app/app.routes.ts (Redirect route)')(format=".")
.l-sub-section
:marked
Learn about the *redirects* in the [Routing](../guide/router.html#!#redirect) chapter.
2015-12-23 12:42:57 -05:00
:marked
Finally, add a dashboard navigation link to the template, just above the *Heroes* link.
2016-06-10 12:37:33 -04:00
+makeExample('toh-5/ts/app/app.component.ts','template', 'app/app.component.ts (template)')(format=".")
2015-12-23 12:42:57 -05:00
.l-sub-section
:marked
2016-06-19 00:20:38 -04:00
We nestled the two links within `<nav>` tags.
2015-12-23 12:42:57 -05:00
They don't do anything yet but they'll be convenient when we style the links a little later in the chapter.
:marked
2016-06-19 00:20:38 -04:00
Refresh the browser. The app displays the dashboard and
2015-12-23 12:42:57 -05:00
we can navigate between the dashboard and the heroes.
## Dashboard Top Heroes
Let’ s spice up the dashboard by displaying the top four heroes at a glance.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
Replace the `template` metadata with a `templateUrl` property that points to a new
2016-03-08 07:45:29 -05:00
template file.
2016-06-19 00:20:38 -04:00
2016-03-08 07:45:29 -05:00
+makeExample('toh-5/ts/app/dashboard.component.ts', 'template-url', 'app/dashboard.component.ts (templateUrl)')(format=".")
.l-sub-section
:marked
2016-06-19 00:20:38 -04:00
We specify the path _all the way back to the application root_ — `app/` in this case —
2016-05-20 18:14:13 -04:00
because Angular doesn't support relative paths _by default_.
2016-06-01 03:51:10 -04:00
We _can_ switch to [component-relative paths](../cookbook/component-relative-paths.html) if we prefer.
2016-03-08 07:45:29 -05:00
:marked
2015-12-23 12:42:57 -05:00
Create that file with these contents:
2016-06-10 12:37:33 -04:00
+makeExample('toh-5/ts/app/dashboard.component.html', null, 'app/dashboard.component.html')(format=".")
2015-12-23 12:42:57 -05:00
:marked
2016-06-19 00:20:38 -04:00
We use `*ngFor` once again to iterate over a list of heroes and display their names.
2015-12-23 12:42:57 -05:00
We added extra `<div>` elements to help with styling later in this chapter.
2016-06-19 00:20:38 -04:00
There's a `(click)` binding to a `gotoDetail` method we haven't written yet and
2015-12-23 12:42:57 -05:00
we're displaying a list of heroes that we don't have.
We have work to do, starting with those heroes.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
### Share the *HeroService*
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
We'd like to re-use the `HeroService` to populate the component's `heroes` array.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
Recall earlier in the chapter that we removed the `HeroService` from the `providers` array of the `HeroesComponent`
and added it to the `providers` array of the top level `AppComponent`.
2016-06-19 00:20:38 -04:00
That move created a singleton `HeroService` instance, available to *all* components of the application.
2016-06-10 12:37:33 -04:00
Angular will inject `HeroService` and we'll use it here in the `DashboardComponent`.
2015-12-23 12:42:57 -05:00
### Get heroes
Open the `dashboard.component.ts` and add the requisite `import` statements.
+makeExample('toh-5/ts/app/dashboard.component.2.ts','imports', 'app/dashboard.component.ts (imports)')(format=".")
:marked
We need `OnInit` interface because we'll initialize the heroes in the `ngOnInit` method as we've done before.
We need the `Hero` and `HeroService` symbols in order to reference those types.
Now implement the `DashboardComponent` class like this:
+makeExample('toh-5/ts/app/dashboard.component.2.ts','component', 'app/dashboard.component.ts (class)')
:marked
We saw this kind of logic before in the `HeroesComponent`.
* create a `heroes` array property
2016-05-03 08:06:32 -04:00
* inject the `HeroService` in the constructor and hold it in a private `heroService` field.
2015-12-23 12:42:57 -05:00
* call the service to get heroes inside the Angular `ngOnInit` lifecycle hook.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
The noteworthy differences: we cherry-pick four heroes (2nd, 3rd, 4th, and 5th) with *slice*
and stub the `gotoDetail` method until we're ready to implement it.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
Refresh the browser and see four heroes in the new dashboard.
2016-06-19 00:20:38 -04:00
.l-main-section
2015-12-23 12:42:57 -05:00
:marked
## Navigate to Hero Details
Although we display the details of a selected hero at the bottom of the `HeroesComponent`,
we don't yet *navigate* to the `HeroDetailComponent` in the three ways specified in our requirements:
1. from the *Dashboard* to a selected hero.
1. from the *Heroes* list to a selected hero.
1. from a "deep link" URL pasted into the browser address bar.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
Adding a `'HeroDetail'` route seem an obvious place to start.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
### Routing to a hero detail
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
We'll add a route to the `HeroDetailComponent` in the `AppComponent` where our other routes are configured.
2016-06-19 00:20:38 -04:00
The new route is a bit unusual in that we must tell the `HeroDetailComponent` *which hero to show*.
2015-12-23 12:42:57 -05:00
We didn't have to tell the `HeroesComponent` or the `DashboardComponent` anything.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
At the moment the parent `HeroesComponent` sets the component's `hero` property to a hero object with a binding like this.
code-example(format='').
<my-hero-detail [hero]="selectedHero"></my-hero-detail>
:marked
2016-06-19 00:20:38 -04:00
That clearly won't work in any of our routing scenarios.
2015-12-23 12:42:57 -05:00
Certainly not the last one; we can't embed an entire hero object in the URL! Nor would we want to.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
### Parameterized route
We *can* add the hero's `id` to the URL. When routing to the hero whose `id` is 11, we could expect to see an URL such as this:
code-example(format='').
/detail/11
:marked
The `/detail/` part of that URL is constant. The trailing numeric `id` part changes from hero to hero.
We need to represent that variable part of the route with a *parameter* (or *token*) that stands for the hero's `id`.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
### Configure a Route with a Parameter
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
Here's the *route definition* we'll use.
2016-06-19 00:20:38 -04:00
+makeExample('toh-5/ts/app/app.routes.1.ts','hero-detail-route', 'app/app.routes.ts (route to HeroDetailComponent)')(format=".")
2015-12-23 12:42:57 -05:00
:marked
2016-06-19 00:20:38 -04:00
The colon (:) in the path indicates that `:id` is a placeholder to be filled with a specific hero `id`
2015-12-23 12:42:57 -05:00
when navigating to the `HeroDetailComponent`.
.l-sub-section
:marked
Of course we have to import the `HeroDetailComponent` before we create this route:
2016-06-19 00:20:38 -04:00
+makeExample('toh-5/ts/app/app.routes.1.ts','hero-detail-import')(format=".")
2015-12-23 12:42:57 -05:00
:marked
2016-06-19 00:20:38 -04:00
We're finished with the application routes.
2015-12-23 12:42:57 -05:00
We won't add a `'Hero Detail'` link to the template because users
don't click a navigation *link* to view a particular hero.
They click a *hero* whether that hero is displayed on the dashboard or in the heroes list.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
We'll get to those *hero* clicks later in the chapter.
There's no point in working on them until the `HeroDetailComponent`
is ready to be navigated *to*.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
That will require an `HeroDetailComponent` overhaul.
2016-06-19 00:20:38 -04:00
.l-main-section
2015-12-23 12:42:57 -05:00
:marked
## Revise the *HeroDetailComponent*
2016-06-19 00:20:38 -04:00
2016-06-10 12:37:33 -04:00
Before we rewrite the `HeroDetailComponent`, let's review what it looks like now:
2015-12-23 12:42:57 -05:00
+makeExample('toh-4/ts/app/hero-detail.component.ts', null, 'app/hero-detail.component.ts (current)')
:marked
The template won't change. We'll display a hero the same way. The big changes are driven by how we get the hero.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
We will no longer receive the hero in a parent component property binding.
2016-06-19 00:20:38 -04:00
The new `HeroDetailComponent` should take the `id` parameter from the `params` observable
in the `ActivatedRoute` service and use the `HeroService` to fetch the hero with that `id`.
2015-12-23 12:42:57 -05:00
2016-06-19 00:20:38 -04:00
We need an import statement to reference the `ActivatedRoute`.
+makeExample('toh-5/ts/app/hero-detail.component.ts', 'import-activated-route')(format=".")
2015-12-23 12:42:57 -05:00
:marked
2016-06-10 12:37:33 -04:00
We import the `HeroService`so we can fetch a hero.
2015-12-23 12:42:57 -05:00
+makeExample('toh-5/ts/app/hero-detail.component.ts', 'import-hero-service')(format=".")
:marked
2016-06-19 00:20:38 -04:00
We import the `OnInit` and `OnDestroy` interfaces because we'll call the `HeroService` inside the `ngOnInit` component lifecycle hook
and we'll clean up our `params` subscription in the `ngOnDestroy`.
2015-12-23 12:42:57 -05:00
+makeExample('toh-5/ts/app/hero-detail.component.ts', 'import-oninit')(format=".")
:marked
2016-06-19 00:20:38 -04:00
We inject the both the `ActivatedRoute` service and the `HeroService` into the constructor as we've done before,
2015-12-23 12:42:57 -05:00
making private variables for both:
+makeExample('toh-5/ts/app/hero-detail.component.ts', 'ctor', 'app/hero-detail.component.ts (constructor)')(format=".")
2016-03-15 08:24:50 -04:00
:marked
2016-06-19 00:20:38 -04:00
We tell the class that we want to implement the `OnInit` and `OnDestroy` interfaces.
2016-03-15 08:24:50 -04:00
+makeExample('toh-5/ts/app/hero-detail.component.ts', 'implement')(format=".")
2015-12-23 12:42:57 -05:00
:marked
2016-06-19 00:20:38 -04:00
Inside the `ngOnInit` lifecycle hook, we _subscribe_ to the `params` observable to
extract the `id` parameter value from the `ActivateRoute` service
2015-12-23 12:42:57 -05:00
and use the `HeroService` to fetch the hero with that `id`.
+makeExample('toh-5/ts/app/hero-detail.component.ts', 'ng-oninit', 'app/hero-detail.component.ts (ngOnInit)')(format=".")
:marked
2016-06-19 00:20:38 -04:00
Inside the `ngOnDestroy` lifecycle hook, we _unsubscribe_ from the `params` subscription.
+makeExample('toh-5/ts/app/hero-detail.component.ts', 'ng-ondestroy', 'app/hero-detail.component.ts (ngOnDestroy)')(format=".")
:marked
Notice how we extract the `id` by calling the `subscribe` method
which will deliver our array of route parameters.
2015-12-23 12:42:57 -05:00
+makeExample('toh-5/ts/app/hero-detail.component.ts', 'get-id')(format=".")
:marked
The hero `id` is a number. Route parameters are *always strings*.
2016-06-19 00:20:38 -04:00
So we convert the route parameter value to a number with the JavaScript (+) operator.
2015-12-23 12:42:57 -05:00
### Add *HeroService.getHero*
The problem with this bit of code is that `HeroService` doesn't have a `getHero` method!
We better fix that quickly before someone notices that we broke the app.
2016-06-19 00:20:38 -04:00
2016-05-29 17:09:06 -04:00
Open `HeroService` and add a `getHero` method that filters the heroes list from `getHeroes` by `id`:
2015-12-23 12:42:57 -05:00
+makeExample('toh-5/ts/app/hero.service.ts', 'get-hero', 'app/hero.service.ts (getHero)')(format=".")
:marked
Return to the `HeroDetailComponent` to clean up loose ends.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
### Find our way back
2016-06-19 00:20:38 -04:00
We can navigate *to* the `HeroDetailComponent` in several ways.
2015-12-23 12:42:57 -05:00
How do we navigate somewhere else when we're done?
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
The user could click one of the two links in the `AppComponent`. Or click the browser's back button.
2016-06-19 00:20:38 -04:00
We'll add a third option, a `goBack` method that navigates backward one step in the browser's history stack
2015-12-23 12:42:57 -05:00
+makeExample('toh-5/ts/app/hero-detail.component.ts', 'go-back', 'app/hero-detail.component.ts (goBack)')(format=".")
.l-sub-section
:marked
2016-06-19 00:20:38 -04:00
Going back too far could take us out of the application.
2015-12-23 12:42:57 -05:00
That's acceptable in a demo. We'd guard against it in a real application,
2016-06-19 00:20:38 -04:00
perhaps with the [*CanDeactivate* guard](../api/router/index/CanDeactivate-interface.html).
2015-12-23 12:42:57 -05:00
:marked
Then we wire this method with an event binding to a *Back* button that we add to the bottom of the component template.
+makeExample('toh-5/ts/app/hero-detail.component.html', 'back-button')(format=".")
:marked
Modifing the template to add this button spurs us to take one more incremental improvement and migrate the template to its own file
called `hero-detail.component.html`
+makeExample('toh-5/ts/app/hero-detail.component.html', '', 'app/hero-detail.component.html')(format=".")
:marked
We update the component metadata with a `templateUrl` pointing to the template file that we just created.
+makeExample('toh-5/ts/app/hero-detail.component.ts', 'template-url', 'app/hero-detail.component.ts (templateUrl)')(format=".")
:marked
Here's the (nearly) finished `HeroDetailComponent`:
+makeExample('toh-5/ts/app/hero-detail.component.ts', 'v2', 'app/hero-detail.component.ts (latest)')(format=".")
.l-main-section
:marked
## Select a *Dashboard* Hero
2016-06-10 12:37:33 -04:00
When a user selects a hero in the dashboard, the app should navigate to the `HeroDetailComponent` to view and edit the selected hero.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
In the dashboard template we bound each hero's click event to the `gotoDetail` method, passing along the selected `hero` entity.
+makeExample('toh-5/ts/app/dashboard.component.html','click', 'app/dashboard.component.html (click binding)')(format=".")
:marked
2016-06-19 00:20:38 -04:00
We stubbed the `gotoDetail` method when we rewrote the `DashboardComponent`.
Now we give it a real implementation.
2015-12-23 12:42:57 -05:00
+makeExample('toh-5/ts/app/dashboard.component.ts','goto-detail', 'app/dashboard.component.ts (gotoDetail)')(format=".")
:marked
The `gotoDetail` method navigates in two steps:
1. set a route *link parameters array*
1. pass the array to the router's navigate method.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
We wrote *link parameters arrays* in the `AppComponent` for the navigation links.
2016-06-19 00:20:38 -04:00
Those arrays had only one element, the path of the destination route.
This array has two elements, the ***path*** of the destination route and a ***route parameter***
2015-12-23 12:42:57 -05:00
with an `id` field set to the value of the selected hero's `id`.
2016-06-19 00:20:38 -04:00
The two array items align with the ***path*** and ***:id*** token in the parameterized `HeroDetail` route configuration we added to `app.routes.ts` earlier in the chapter.
+makeExample('toh-5/ts/app/app.routes.1.ts','hero-detail-route', 'app/app.routes.ts (hero detail route)')(format=".")
2015-12-23 12:42:57 -05:00
:marked
2016-06-19 00:20:38 -04:00
The `DashboardComponent` doesn't have the router yet. We obtain it in the usual way:
2016-06-10 12:37:33 -04:00
import the `router` reference and inject it in the constructor (along with the `HeroService`):
2015-12-23 12:42:57 -05:00
+makeExample('toh-5/ts/app/dashboard.component.ts','import-router', 'app/dashboard.component.ts (excerpts)')(format=".")
+makeExample('toh-5/ts/app/dashboard.component.ts','ctor')(format=".")
:marked
Refresh the browser and select a hero from the dashboard; the app should navigate directly to that hero’ s details.
.l-main-section
:marked
## Select a Hero in the *HeroesComponent*
We'll do something similar in the `HeroesComponent`.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
That component's current template exhibits a "master/detail" style with the list of heroes
at the top and details of the selected hero below.
+makeExample('toh-4/ts/app/app.component.ts','template', 'app/heroes.component.ts (current template)')(format=".")
:marked
Delete the last line of the template with the `<my-hero-detail>` tags.
2016-06-19 00:20:38 -04:00
We'll no longer show the full `HeroDetailComponent` here.
2015-12-23 12:42:57 -05:00
We're going to display the hero detail on its own page and route to it as we did in the dashboard.
2016-06-19 00:20:38 -04:00
But we'll throw in a small twist for variety.
2015-12-23 12:42:57 -05:00
When the user selects a hero from the list, we *won't* go to the detail page.
We'll show a *mini-detail* on *this* page instead and make the user click a button to navigate to the *full detail *page.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
### Add the *mini-detail*
Add the following HTML fragment at the bottom of the template where the `<my-hero-detail>` used to be:
+makeExample('toh-5/ts/app/heroes.component.html','mini-detail')(format=".")
:marked
After clicking a hero, the user should see something like this below the hero list:
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
figure.image-display
img(src='/resources/images/devguide/toh/mini-hero-detail.png' alt="Mini Hero Detail" height="70")
:marked
### Format with the *UpperCasePipe*
2016-06-19 00:20:38 -04:00
2016-03-08 07:53:01 -05:00
Notice that the hero's name is displayed in CAPITAL LETTERS. That's the effect of the `UpperCasePipe`
2016-06-10 12:37:33 -04:00
that we slipped into the interpolation binding. Look for it right after the pipe operator ( | ).
2015-12-23 12:42:57 -05:00
+makeExample('toh-5/ts/app/heroes.component.html','pipe')(format=".")
:marked
Pipes are a good way to format strings, currency amounts, dates and other display data.
Angular ships with several pipes and we can write our own.
.l-sub-section
:marked
Learn about pipes in the [Pipes](../guide/pipes.html) chapter.
:marked
### Move content out of the component file
We are not done. We still have to update the component class to support navigation to the
`HeroDetailComponent` when the user clicks the *View Details* button.
2016-06-19 00:20:38 -04:00
This component file is really big. Most of it is either template or CSS styles.
2015-12-23 12:42:57 -05:00
It's difficult to find the component logic amidst the noise of HTML and CSS.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
Let's migrate the template and the styles to their own files before we make any more changes:
1. *Cut-and-paste* the template contents into a new `heroes.component.html` file.
1. *Cut-and-paste* the styles contents into a new `heroes.component.css` file.
1. *Set* the component metadata's `templateUrl` and `styleUrls` properties to refer to both files.
2016-06-19 00:20:38 -04:00
Because the template for `HeroesComponent` no longer uses `HeroDetailComponent`
directly — instead using the router to _navigate_ to it — we can
2016-06-15 10:46:26 -04:00
remove `HeroDetailComponent` from the directives list. That
list is now empty, so we can remove the `directives` property. The revised
`@Component` looks like this:
2015-12-23 12:42:57 -05:00
+makeExample('toh-5/ts/app/heroes.component.ts', 'metadata', 'app/heroes.component.ts (revised metadata)')(format=".")
:marked
Now we can see what's going on as we update the component class along the same lines as the dashboard:
1. Import the `router`
1. Inject the `router` in the constructor (along with the `HeroService`)
2016-06-19 00:20:38 -04:00
1. Implement the `gotoDetail` method by calling the `router.navigate` method
2016-06-10 12:37:33 -04:00
with a two-part `HeroDetail` *link parameters array*.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
Here's the revised component class:
+makeExample('toh-5/ts/app/heroes.component.ts', 'class', 'app/heroes.component.ts (class)')
:marked
2016-06-19 00:20:38 -04:00
Refresh the browser and start clicking.
2015-12-23 12:42:57 -05:00
We can navigate around the app, from the dashboard to hero details and back,
2016-06-19 00:20:38 -04:00
for heroes list to the mini-detail to the hero details and back to the heroes again.
2015-12-23 12:42:57 -05:00
We can jump back and forth between the dashboard and the heroes.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
We've met all of the navigational requirements that propelled this chapter.
.l-main-section
:marked
## Styling the App
The app is functional but pretty ugly.
Our creative designer team provided some CSS files to make it look better.
### A Dashboard with Style
2016-06-19 00:20:38 -04:00
The designers think we should display the dashboard heroes in a row of rectangles.
2015-12-23 12:42:57 -05:00
They've given us ~60 lines of CSS for this purpose including some simple media queries for responsive design.
If we paste these ~60 lines into the component `styles` metadata,
2016-06-19 00:20:38 -04:00
they'll completely obscure the component logic.
Let's not do that. It's easier to edit CSS in a separate `*.css` file anyway.
2015-12-23 12:42:57 -05:00
2016-06-19 00:20:38 -04:00
Add a `dashboard.component.css` file to the `app` folder and reference
2015-12-23 12:42:57 -05:00
that file in the component metadata's `styleUrls` array property like this:
+makeExample('toh-5/ts/app/dashboard.component.ts', 'css', 'app/dashboard.component.ts (styleUrls)')(format=".")
:marked
.l-sub-section
:marked
The `styleUrls` property is an array of style file names (with paths).
We could list multiple style files from different locations if we needed them.
2016-03-08 18:09:31 -05:00
As with `templateUrl`, we must specify the path _all the way back to the application root_.
2015-12-23 12:42:57 -05:00
:marked
### Stylish Hero Details
The designers also gave us CSS styles specifically for the `HeroDetailComponent`.
2016-06-19 00:20:38 -04:00
Add a `hero-detail.component.css` to the `app` folder and refer to that file inside
2015-12-23 12:42:57 -05:00
the `styleUrls` array as we did for `DashboardComponent`.
2016-06-19 00:20:38 -04:00
Here's the content for the aforementioned component CSS files.
2015-12-23 12:42:57 -05:00
+makeTabs(
`toh-5/ts/app/hero-detail.component.css,
toh-5/ts/app/dashboard.component.css`,
null,
`app/hero-detail.component.css,
app/dashboard.component.css`)
:marked
### Style the Navigation Links
The designers gave us CSS to make the navigation links in our `AppComponent` look more like selectable buttons.
We cooperated by surrounding those links in `<nav>` tags.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
Add a `app.component.css` file to the `app` folder with the following content.
2016-06-10 12:37:33 -04:00
+makeExample('toh-5/ts/app/app.component.css', '', 'app/app.component.css (navigation styles)')
2015-12-23 12:42:57 -05:00
.l-sub-section
:marked
2016-06-19 00:20:38 -04:00
**The *routerLinkActive* directive**
The Angular Router provides a `routerLinkActive` directive we can use to
to add a class to the HTML navigation element whose route matches the active route.
All we have to do is define the style for it. Sweet!
+makeExample('toh-5/ts/app/app.component.3.ts', 'router-link-active', 'app/app.component.ts (active router links)')(format=".")
2015-12-23 12:42:57 -05:00
:marked
2016-06-19 00:20:38 -04:00
Set the `AppComponent`’ s `styleUrls` property to this CSS file.
2015-12-23 12:42:57 -05:00
+makeExample('toh-5/ts/app/app.component.ts','style-urls', 'app/app.component.ts (styleUrls)')(format=".")
:marked
### Global application styles
2016-06-19 00:20:38 -04:00
When we add styles to a component, we're keeping everything a component needs
2015-12-23 12:42:57 -05:00
— HTML, the CSS, the code — together in one convenient place.
It's pretty easy to package it all up and re-use the component somewhere else.
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
We can also create styles at the *application level* outside of any component.
2016-06-10 12:37:33 -04:00
Our designers provided some basic styles to apply to elements across the entire app.
2016-06-19 00:20:38 -04:00
These correspond to the full set of master styles that we
introduced earlier (see
2016-06-10 12:37:33 -04:00
[QuickStart, "Add some style"](../quickstart.html#!#add-some-style)).
Here is an excerpt.
+makeExample('toh-5/ts/styles.1.css', 'toh-excerpt', 'styles.css (app styles excerpt)')(format=".")
- var styles_css = 'https://raw.githubusercontent.com/angular/angular.io/master/public/docs/_examples/styles.css'
2015-12-23 12:42:57 -05:00
:marked
2016-06-10 12:37:33 -04:00
Add a new file named `styles.css` in the root folder, if there isn't one already.
Ensure that it contains the [master styles given here](!{styles_css}).
2016-06-15 10:46:26 -04:00
If necessary, also edit `index.html` to refer to this stylesheet.
2015-12-23 12:42:57 -05:00
+makeExample('toh-5/ts/index.html','css', 'index.html (link ref)')(format=".")
:marked
Look at the app now. Our dashboard, heroes, and navigation links are styling!
2016-06-19 00:20:38 -04:00
2015-12-23 12:42:57 -05:00
figure.image-display
img(src='/resources/images/devguide/toh/dashboard-top-heroes.png' alt="View navigations")
.l-main-section
:marked
## Application structure and code
2016-06-07 18:51:25 -04:00
p.
2016-06-15 10:46:26 -04:00
Review the sample source code in the #[+liveExampleLink2('', 'toh-5')] for this chapter.
2015-12-23 12:42:57 -05:00
Verify that we have the following structure:
2016-06-07 18:51:25 -04:00
:marked
2015-12-23 12:42:57 -05:00
.filetree
.file angular2-tour-of-heroes
.children
.file app
.children
.file app.component.ts
.file app.component.css
2016-06-19 00:20:38 -04:00
.file app.routes.ts
2015-12-23 12:42:57 -05:00
.file dashboard.component.css
.file dashboard.component.html
.file dashboard.component.ts
.file hero.ts
.file hero-detail.component.css
.file hero-detail.component.html
.file hero-detail.component.ts
.file hero.service.ts
.file heroes.component.css
.file heroes.component.html
.file heroes.component.ts
.file main.ts
.file mock-heroes.ts
2016-02-11 18:08:06 -05:00
.file node_modules ...
2016-06-19 00:20:38 -04:00
.file typings ...
2015-12-23 12:42:57 -05:00
.file index.html
.file package.json
2016-02-27 16:48:24 -05:00
.file styles.css
2016-04-09 00:18:37 -04:00
.file systemjs.config.json
2015-12-23 12:42:57 -05:00
.file tsconfig.json
2016-02-11 18:08:06 -05:00
.file typings.json
2015-12-23 12:42:57 -05:00
.l-main-section
:marked
## Recap
### The Road Behind
2016-03-15 08:24:50 -04:00
We travelled a great distance in this chapter.
2015-12-23 12:42:57 -05:00
- We added the Angular *Component Router* to navigate among different components.
2016-06-10 12:37:33 -04:00
- We learned how to create router links to represent navigation menu items.
- We used router parameters to navigate to the details of user selected hero.
- We shared the `HeroService` among multiple components.
2015-12-23 12:42:57 -05:00
- We moved HTML and CSS out of the component file and into their own files.
2016-06-10 12:37:33 -04:00
- We added the `uppercase` pipe to format data.
2015-12-23 12:42:57 -05:00
### The Road Ahead
We have much of the foundation we need to build an application.
2016-06-19 00:20:38 -04:00
We're still missing a key piece: remote data access.
In the next chapter,
2016-04-27 14:28:22 -04:00
we’ ll replace our mock data with data retrieved from a server using http.