@ -2,7 +2,7 @@
block includes
include ../_util-fns
- var _appRoutingTsVsAppComp = 'app-routing .module.ts'
- var _appRoutingTsVsAppComp = 'app.module.ts'
- var _declsVsDirectives = 'declarations'
- var _RoutesVsAtRouteConfig = 'Routes'
- var _RouterModuleVsRouterDirectives = 'RouterModule'
@ -24,7 +24,7 @@ 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.
We'll add Angular’ s *Router* to our app to satisfy these requirements.
.l-sub-section
:marked
@ -52,7 +52,7 @@ figure.image-display
block intro-file-tree
.filetree
.file angular2 -tour-of-heroes
.file angular-tour-of-heroes
.children
.file app
.children
@ -164,7 +164,7 @@ block app-comp-v1
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.
We'll need the Angular *Component Router*.
We'll need the Angular *Router*.
block angular-router
:marked
@ -192,7 +192,7 @@ block router-config-intro
### Configure routes
Our application doesn't have any routes yet.
We'll start by creating a configuration file f or the application routes.
We'll start by creating a configuration for the application routes.
:marked
*Routes* tell the router which views to display when a user clicks a link or
@ -200,7 +200,7 @@ block router-config-intro
Let's define our first route as a route to the heroes component:
- var _file = _docsFor == 'dart' ? 'app.component.ts' : 'app-routing .module.ts'
- var _file = _docsFor == 'dart' ? 'app.component.ts' : 'app.module.2 .ts'
+makeExcerpt('app/' + _file + ' (heroes route)', 'heroes')
- var _are = _docsFor == 'dart' ? 'takes' : 'are'
@ -222,24 +222,18 @@ block router-config-intro
+ifDocsFor('ts|js')
:marked
We'll export a `routing` constant initialized using the `RouterModule.forRoot` method applied to our !{_array} of routes.
This method returns a **configured router module** that we'll add to our root NgModule, `AppModule`.
### Make the router available
+makeExcerpt('app/app-routing.module.1.ts (excerpt)', 'routing-export')
We've setup the initial route configuration. Now we'll add it to our `AppModule`.
We'll add our configured `RouterModule` to the `AppModule` imports !{_array}.
+makeExcerpt('app/app.module.2.ts (app routing)', '')
.l-sub-section
:marked
We call the `forRoot` method because we're providing a configured router at the _root_ of the application.
The `forRoot` method gives us the Router service providers and directives needed for routing.
:marked
### Make the router available
We've setup initial routes in the `app-routing.module.ts` file. Now we'll add it to our root NgModule.
Import the `routing` constant from `app-routing.module.ts` and add it the `imports` !{_array} of `AppModule`.
+makeExcerpt('app/app.module.ts', 'routing')
We use the `forRoot` method because we're providing a configured router at the _root_ of the application.
The `forRoot` method gives us the Router service providers and directives needed for routing, and
performs the initial navigation based on the current browser URL.
- var _heroesRoute = _docsFor == 'dart' ? "'Heroes'" : 'heroes'
:marked
@ -317,12 +311,12 @@ block routerLink
Import the dashboard component and
add the following route definition to the `!{_RoutesVsAtRouteConfig}` !{_array} of definitions.
- var _file = _docsFor == 'dart' ? 'lib/app_component.dart' : 'app/app-routing .module.ts'
- var _file = _docsFor == 'dart' ? 'lib/app_component.dart' : 'app/app.module.3 .ts'
+makeExcerpt(_file + ' (Dashboard route)', 'dashboard')
+ifDocsFor('ts|js')
:marked
Also import and add `DashboardComponent` to our root NgModule 's `declarations`.
Also import and add `DashboardComponent` to our `AppModule` 's `declarations`.
+makeExcerpt('app/app.module.ts', 'dashboard')
@ -338,7 +332,7 @@ block redirect-vs-use-as-default
We can use a redirect route to make this happen. Add the following
to our array of route definitions:
+makeExcerpt('app/app-routing .module.ts','redirect')
+makeExcerpt('app/app.module.3 .ts','redirect')
.l-sub-section
:marked
@ -354,7 +348,7 @@ block redirect-vs-use-as-default
.l-sub-section
:marked
We nestl ed the two links within `<nav>` tags.
We nested the two links within `<nav>` tags.
They don't do anything yet but they'll be convenient when we style the links a little later in the chapter.
:marked
@ -367,30 +361,24 @@ block redirect-vs-use-as-default
Replace the `template` metadata with a `templateUrl` property that points to a new
template file.
+makeExcerpt('app/dashboard.component.ts', 'templateUrl')
.l-sub-section
block templateUrl-path-resolution
+ifDocsFor('ts|js')
:marked
We specify the path _all the way back to the application root_ —
<span if-docs="ts">`app/` in this case —</span>
because Angular doesn't support relative paths _by default_.
We _can_ switch to [component-relative paths](../cookbook/component-relative-paths.html) if we prefer.
Set the `moduleId` property to `module.id` for module-relative loading of the `templateUrl`.
+makeExcerpt('app/dashboard.component.ts', 'metadata')
block templateUrl-path-resolution
//- N/A for TS
:marked
Create that file with this content:
+makeExcerpt( 'app/dashboard.component.html')
+makeExample('app/dashboard.component.1.html', '', 'app/dashboard.component.html')
:marked
We use `*ngFor` once again to iterate over a list of heroes and display their names.
We added extra `<div>` elements to help with styling later in this chapter.
There's a `(click)` binding to a `gotoDetail` method we haven't written yet and
we're displaying a list of heroes that we don't have.
We have work to do, starting with those heroes.
### Share the *HeroService*
We'd like to re-use the `HeroService` to populate the component's `heroes` !{_array}.
@ -405,12 +393,12 @@ block redirect-vs-use-as-default
Open <span ngio-ex>dashboard.component.ts</span> and add the requisite `import` statements.
+makeExcerpt('app/dashboard.component.2. ts','imports')
+makeExcerpt('app/dashboard.component.ts','imports')
:marked
Now implement the `DashboardComponent` class like this:
+makeExcerpt('app/dashboard.component.2.ts (class)', 'component ')
+makeExcerpt('app/dashboard.component.ts (class)', 'class ')
:marked
We've seen this kind of logic before in the `HeroesComponent`:
@ -419,8 +407,7 @@ block redirect-vs-use-as-default
* Inject the `HeroService` in the constructor and hold it in a private `!{_priv}heroService` field.
* Call the service to get heroes inside the Angular `ngOnInit` lifecycle hook.
The noteworthy differences: we cherry-pick four heroes (2nd, 3rd, 4th, and 5th)
and stub the `gotoDetail` method until we're ready to implement it.
In this dashboard we cherry-pick four heroes (2nd, 3rd, 4th, and 5th)<span if-docs="ts"> with the `Array.slice` method</span>.
Refresh the browser and see four heroes in the new dashboard.
@ -470,7 +457,7 @@ code-example(format='').
Here's the *route definition* we'll use.
- var _file = _docsFor == 'dart' ? 'app/app.component.ts' : 'app/app-routing .module.ts'
- var _file = _docsFor == 'dart' ? 'app/app.component.ts' : 'app/app.module.3 .ts'
+makeExcerpt(_file + ' (hero detail)','hero-detail')
:marked
@ -521,7 +508,7 @@ block route-params
- var _ActivatedRoute = _docsFor == 'dart' ? 'RouteParams' : 'ActivatedRoute'
:marked
Let's have the `!{_ActivatedRoute}` service and the `HeroService` injected
Let's have the `!{_ActivatedRoute}` service, the `HeroService` and the `Location` service injected
into the constructor, saving their values in private fields:
+makeExcerpt('app/hero-detail.component.ts (constructor)', 'ctor')
@ -534,7 +521,7 @@ block route-params
block ngOnInit
:marked
Inside the `ngOnInit` lifecycle hook, we use the `params` observable to
extract the `id` parameter value from the `ActivateRoute` service
extract the `id` parameter value from the `Activated Route` service
and use the `HeroService` to fetch the hero with that `id`.
+makeExcerpt('app/hero-detail.component.ts', 'ngOnInit')
@ -567,7 +554,8 @@ block extract-id
How do we navigate somewhere else when we're done?
The user could click one of the two links in the `AppComponent`. Or click the browser's back button.
We'll add a third option, a `goBack` method that navigates backward one step in the browser's history stack.
We'll add a third option, a `goBack` method that navigates backward one step in the browser's history stack
using the `Location` service we injected previously.
+makeExcerpt('app/hero-detail.component.ts', 'goBack')
@ -586,16 +574,16 @@ block extract-id
+makeExcerpt('app/hero-detail.component.html', 'back-button', '')
:marked
Modifing the template to add this button spurs us to take one more
Modify ing the template to add this button spurs us to take one more
incremental improvement and migrate the template to its own file,
called <span ngio-ex>hero-detail.component.html</span>:
+makeExample('app/hero-detail.component.html')
:marked
We update the component metadata with a `templateUrl` pointing to the template file that we just created.
We update the component metadata with a <span if-docs="ts">`moduleId` and a </span> `templateUrl` pointing to the template file that we just created.
+makeExcerpt('app/hero-detail.component.ts', 'templateUrl ')
+makeExcerpt('app/hero-detail.component.ts', 'metadata ')
:marked
Refresh the browser and see the results.
@ -606,71 +594,118 @@ block extract-id
When a user selects a hero in the dashboard, the app should navigate to the `HeroDetailComponent` to view and edit the selected hero.
In the dashboard template we bound each hero's click event to the `gotoDetail` method, passing along the selected `hero` entity.
Although the dashboard heroes are presented as button-like blocks, they should behave like anchor tags.
When hovering over a hero block, the target URL should display in the browser status bar
and the user should be able to copy the link or open the hero detail view in a new tab.
+makeExcerpt('app/dashboard.component.html', 'click')
To achieve this effect, reopen the `dashboard.component.html` and replace the repeated `<div *ngFor...>` tags
with `<a>` tags. The opening `<a>` tag looks like this:
+makeExample('app/dashboard.component.html', 'click', 'app/dashboard.component.html (repeated <a> tag)')
+ifDocsFor('dart')
.alert.is-important
:marked
We stubbed the `gotoDetail` method when we rewrote the `DashboardComponent`.
Now we give it a real implementation.
+makeExcerpt('app/dashboard.component.ts','gotoDetail')
Router links in the dashboard are currently not operational, as reported in issue
[dart-lang/angular2/issues/186](https://github.com/dart-lang/angular2/issues/186).
- var _pathVsName = _docsFor == 'dart' ? 'name' : 'path'
:marked
The `gotoDetail` method navigates in two steps:
Notice the `[routerLink]` binding.
1. Set a route *link parameters !{_array}*
1. Pass the !{_array} to the router's navigate method
Top level navigation in the [`AppComponent`
template](#router-links) has router links set to fixed !{_pathVsName}s of the
destination routes, "/dashboard" and "/heroes".
For navigation, we wrote router links <span if-docs="dart">as *link
parameters !{_array}s*</span> in the [`AppComponent`
template](#router-links). Those link<span if-docs="dart"> parameters
!{_array}</span>s had only one element, the !{_pathVsName} of the
destination route.
This link parameters !{_array} has two elements, the ***!{_pathVsName}*** of
the destination route and a ***route parameter*** <span if-docs="dart">with
an `id` field</span> set to the value of the selected hero's `id`.
This time, we're binding to an expression containing a **link parameters !{_array}**.
The !{_array} has two elements, the ***!{_pathVsName}*** of
the destination route and a ***route parameter*** set to the value of the current hero's `id`.
The two !{_array} items align with the ***!{_pathVsName}*** and ***:id***
token in the parameterized hero detail route definition we added to
`!{_appRoutingTsVsAppComp}` earlier in the chapter:
- var _file = _docsFor == 'dart' ? 'app/app.component.ts' : 'app/app-routing .module.ts'
- var _file = _docsFor == 'dart' ? 'app/app.component.ts' : 'app/app.module.3.ts'
+makeExcerpt(_file + ' (hero detail)', 'hero-detail')
:marked
The `DashboardComponent` doesn't have the router yet. We obtain it in the usual way:
import the `router` reference and inject it in the constructor (along with the `HeroService`):
+makeExcerpt('app/dashboard.component.ts ()','import-router', '')
+makeExcerpt('app/dashboard.component.ts', 'ctor', '')
:marked
Refresh the browser and select a hero from the dashboard; the app should navigate directly to that hero’ s details.
+ifDocsFor('ts')
.l-main-section
:marked
## Refactor routes to a _Routing Module_
Almost 20 lines of `AppModule` are devoted to configuring four routes.
Most applications have many more routes and they [add guard services](../guide/router.html#guards)
to protect against unwanted or unauthorized navigations.
Routing considerations could quickly dominate this module and obscure its primary purpose which is to
establish key facts about the entire app for the Angular compiler.
We should refactor the routing configuration into its own class.
What kind of class?
The current `RouterModule.forRoot()` produces an Angular `ModuleWithProviders` which suggests that a
class dedicated to routing should be some kind of module.
It should be a [_Routing Module_](../guide/router.html#routing-module).
By convention the name of a _Routing Module_ contains the word "Routing" and
aligns with the name of the module that declares the components navigated to.
Create an `app-routing.module.ts` file as a sibling to `app.module.ts`. Give it the following contents extracted from the `AppModule` class:
+makeExample('app/app-routing.module.ts')
:marked
Noteworthy points, typical of _Routing Modules_:
* Pull the routes into a variable. You might export it in future and it clarifies the _Routing Module_ pattern.
* Add `RouterModule.forRoot(routes)` to `imports`.
* Add `RouterModule` to `exports` so that the components in the companion module have access to Router declarables
such as `RouterLink` and `RouterOutlet`.
* No `declarations`! Declarations are the responsibility of the companion module.
* Add module `providers` for guard services if you have them; there are none in this example.
### Update _AppModule_
Now delete the routing configuration from `AppModule` and import the `AppRoutingModule`
(_both_ with an ES `import` statement _and_ by adding it to the `NgModule.imports` list).
Here is the revised `AppModule`, compared to its pre-refactor state:
+makeTabs(
`toh-5/ts/app/app.module.ts, toh-5/ts/app/app.module.3.ts`,
null,
`app/app.module.ts (after), app/app.module.ts (before)`)
:marked
It's simpler and focused on indentifying the key pieces of the application.
.l-main-section
:marked
## Select a Hero in the *HeroesComponent*
Earlier we added the ability to select a hero from the dashboard.
We'll do something similar in the `HeroesComponent`.
That component's current template exhibits a "master/detail" style with the list of heroes
The `HeroesComponent` 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
Our goal is to move the detail to its own view and navigate to it when the user decides to edit a selected hero.
Delete the `<h1>` at the top (we forgot about it during the `AppComponent`-to-`HeroesComponent` conversion).
Delete the last line of the template with the `<my-hero-detail>` tags.
We'll no longer show the full `HeroDetailComponent` here.
We're going to display the hero detail on its own page and route to it as we did in the dashboard.
But we'll throw in a small twist for variety.
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.
We'll throw in a small twist for variety.
We are keeping the "master/detail" style but shrinking the detail to a "mini", read-only version.
When the user selects a hero from the list, we *don't* go to the detail page.
We show a *mini-detail* on *this* page instead and make the user click a button to navigate to the *full detail *page.
### Add the *mini-detail*
@ -714,13 +749,12 @@ figure.image-display
1. *Cut-and-paste* the template contents into a new <span ngio-ex>heroes.component.html</span> file.
1. *Cut-and-paste* the styles contents into a new <span ngio-ex>heroes.component.css</span> file.
1. *Set* the component metadata's `templateUrl` and `styleUrls` properties to refer to both files.
<li if-docs="ts">. *Set* the `moduleId` property to `module.id` so that `templateUrl` and `styleUrls` are relative to the component.</li>
.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.
<span if-docs="ts">As with `templateUrl`, we must specify the path _all the way
back to the application root_.</span>
block heroes-component-cleanup
//- Only relevant for Dart.
@ -728,15 +762,26 @@ block heroes-component-cleanup
+makeExcerpt('app/heroes.component.ts (revised metadata)', 'metadata')
:marked
Now we can see what's going on as we update the component class along the same lines as the dashboard:
### Update the _HeroesComponent_ class.
1. Import the `router`
The `HeroesComponent` navigates to the `HeroesDetailComponent` in response to a button click.
The button's _click_ event is bound to a `gotoDetail` method that navigates _imperatively_
by telling the router where to go.
This approach requires some changes to the component class:
1. Import the `router` from the Angular router library
1. Inject the `router` in the constructor (along with the `HeroService`)
1. Implement the `gotoDetail` method by calling the `router.navigate` method
1. Implement `gotoDetail` by calling the `router.navigate` method
with a two-part hero-detail link parameters !{_array}.
+makeExcerpt('app/heroes.component.ts', 'gotoDetail')
Here's the revised component class:
:marked
Note that we're passing a two-element **link parameters !{_array}**
— a path and the route parameter — to
the `router.navigate` method just as we did in the `[routerLink]` binding
back in the `DashboardComponent`.
Here's the fully revised `HeroesComponent` class:
+makeExcerpt('app/heroes.component.ts', 'class')
@ -834,7 +879,7 @@ block css-files
+makeExcerpt('styles.css (excerpt)', 'toh')
- var styles_css = 'https://raw.githubusercontent.com/angular/angular.io/master/public/docs/_examples/styles.css'
- var styles_css = 'https://raw.githubusercontent.com/angular/angular.io/master/public/docs/_examples/_boilerplate/ styles.css'
:marked
Create the file <span ngio-ex>styles.css</span>, if it doesn't exist already.
@ -859,7 +904,7 @@ figure.image-display
block file-tree-end
.filetree
.file angular2 -tour-of-heroes
.file angular-tour-of-heroes
.children
.file app
.children
@ -895,12 +940,13 @@ block file-tree-end
We travelled a great distance in this chapter
- We added the Angular *Component Router* to navigate among different components.
- We added the Angular *Router* to navigate among different components.
- We learned how to create router links to represent navigation menu items.
- We used router link parameters to navigate to the details of user selected hero.
- We shared the `HeroService` among multiple components.
- We moved HTML and CSS out of the component file and into their own files.
- We added the `uppercase` pipe to format data.
<li if-docs="ts"> We refactored routes into a `Routing Module` that we import.</li>
### The Road Ahead