docs(toh-5/dart): use routerLink in dashboard (#2744)

* docs(toh-5/dart): use routerLink in dashboard

* minor edits to TS jade

* remove dart/toh-pt5 from bad-code-excerpt-skip-patterns
This commit is contained in:
Patrice Chalin 2016-11-08 14:48:03 -08:00 committed by Kathy Walrath
parent 2808878c36
commit c24dd074a6
10 changed files with 218 additions and 154 deletions

View File

@ -4,21 +4,27 @@
import 'dart:async'; import 'dart:async';
import 'package:angular2/core.dart'; import 'package:angular2/core.dart';
// #docregion import-router
import 'package:angular2/router.dart';
// #enddocregion import-router
import 'hero.dart'; import 'hero.dart';
import 'hero_service.dart'; import 'hero_service.dart';
// #enddocregion imports // #enddocregion imports
// #docregion metadata
@Component( @Component(
selector: 'my-dashboard', selector: 'my-dashboard',
// #docregion templateUrl // #docregion templateUrl
templateUrl: 'dashboard_component.html', templateUrl: 'dashboard_component.html',
// #enddocregion templateUrl // #enddocregion templateUrl
// #docregion css // #docregion css
styleUrls: const ['dashboard_component.css'] styleUrls: const ['dashboard_component.css'],
// #enddocregion css // #enddocregion css
directives: const [ROUTER_DIRECTIVES],
) )
// #docregion component // #enddocregion metadata
// #docregion class, component
class DashboardComponent implements OnInit { class DashboardComponent implements OnInit {
List<Hero> heroes; List<Hero> heroes;
@ -26,11 +32,9 @@ class DashboardComponent implements OnInit {
final HeroService _heroService; final HeroService _heroService;
DashboardComponent(this._heroService); DashboardComponent(this._heroService);
// #enddocregion ctor // #enddocregion ctor
Future<Null> ngOnInit() async { Future<Null> ngOnInit() async {
heroes = (await _heroService.getHeroes()).skip(1).take(4).toList(); heroes = (await _heroService.getHeroes()).skip(1).take(4).toList();
} }
} }
// #enddocregion component

View File

@ -2,7 +2,7 @@
<h3>Top Heroes</h3> <h3>Top Heroes</h3>
<div class="grid grid-pad"> <div class="grid grid-pad">
<!-- #docregion click --> <!-- #docregion click -->
<a *ngFor="let hero of heroes" [routerLink]="['/detail', hero.id]" class="col-1-4"> <a *ngFor="let hero of heroes" [routerLink]="['./HeroDetail', {id: hero.id}]" class="col-1-4">
<!-- #enddocregion click --> <!-- #enddocregion click -->
<div class="module hero"> <div class="module hero">
<h4>{{hero.name}}</h4> <h4>{{hero.name}}</h4>

View File

@ -2,12 +2,12 @@
// #docregion , v2 // #docregion , v2
// #docregion added-imports // #docregion added-imports
import 'dart:async'; import 'dart:async';
import 'dart:html' show window;
// #enddocregion added-imports // #enddocregion added-imports
import 'package:angular2/core.dart'; import 'package:angular2/core.dart';
// #docregion added-imports // #docregion added-imports
import 'package:angular2/router.dart'; import 'package:angular2/router.dart';
import 'package:angular2/platform/common.dart';
// #enddocregion added-imports // #enddocregion added-imports
import 'hero.dart'; import 'hero.dart';
@ -17,9 +17,9 @@ import 'hero_service.dart';
@Component( @Component(
selector: 'my-hero-detail', selector: 'my-hero-detail',
// #docregion templateUrl // #docregion metadata, templateUrl
templateUrl: 'hero_detail_component.html', templateUrl: 'hero_detail_component.html',
// #enddocregion templateUrl, v2 // #enddocregion metadata, templateUrl, v2
styleUrls: const ['hero_detail_component.css'] styleUrls: const ['hero_detail_component.css']
// #docregion v2 // #docregion v2
) )
@ -30,8 +30,9 @@ class HeroDetailComponent implements OnInit {
// #docregion ctor // #docregion ctor
final HeroService _heroService; final HeroService _heroService;
final RouteParams _routeParams; final RouteParams _routeParams;
final Location _location;
HeroDetailComponent(this._heroService, this._routeParams); HeroDetailComponent(this._heroService, this._routeParams, this._location);
// #enddocregion ctor // #enddocregion ctor
// #docregion ngOnInit // #docregion ngOnInit
@ -44,7 +45,7 @@ class HeroDetailComponent implements OnInit {
// #docregion goBack // #docregion goBack
void goBack() { void goBack() {
window.history.back(); _location.back();
} }
// #enddocregion goBack // #enddocregion goBack
} }

View File

@ -41,9 +41,11 @@ class HeroesComponent implements OnInit {
selectedHero = hero; selectedHero = hero;
} }
// #docregion gotoDetail
Future<Null> gotoDetail() => _router.navigate([ Future<Null> gotoDetail() => _router.navigate([
'HeroDetail', 'HeroDetail',
{'id': selectedHero.id.toString()} {'id': selectedHero.id.toString()}
]); ]);
// #enddocregion gotoDetail
// #docregion renaming // #docregion renaming
} }

View File

@ -33,4 +33,3 @@ export class DashboardComponent implements OnInit {
.then(heroes => this.heroes = heroes.slice(1, 5)); .then(heroes => this.heroes = heroes.slice(1, 5));
} }
} }
// #enddocregion class

View File

@ -96,12 +96,13 @@ block redirect-vs-use-as-default
router will display the dashboard when the browser URL doesn't match an existing route. router will display the dashboard when the browser URL doesn't match an existing route.
block templateUrl-path-resolution block templateUrl-path-resolution
:marked .l-sub-section
The value of `templateUrl` can be an [asset][] in this package or another :marked
package. To use an asset in another package, use a full package reference, The value of `templateUrl` can be an [asset][] in this package or another
such as `'package:some_other_package/dashboard_component.html'`. package. To use an asset in another package, use a full package reference,
such as `'package:some_other_package/dashboard_component.html'`.
[asset]: https://www.dartlang.org/tools/pub/glossary#asset [asset]: https://www.dartlang.org/tools/pub/glossary#asset
block route-params block route-params
:marked :marked

View File

@ -2,7 +2,7 @@
block includes block includes
include ../_util-fns include ../_util-fns
- var _appRoutingTsVsAppComp = 'app-routing.module.ts' - var _appRoutingTsVsAppComp = 'app.module.ts'
- var _declsVsDirectives = 'declarations' - var _declsVsDirectives = 'declarations'
- var _RoutesVsAtRouteConfig = 'Routes' - var _RoutesVsAtRouteConfig = 'Routes'
- var _RouterModuleVsRouterDirectives = 'RouterModule' - var _RouterModuleVsRouterDirectives = 'RouterModule'
@ -24,7 +24,7 @@ figure.image-display
img(src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations") img(src='/resources/images/devguide/toh/nav-diagram.png' alt="View navigations")
:marked :marked
We'll add Angulars *Component Router* to our app to satisfy these requirements. We'll add Angulars *Router* to our app to satisfy these requirements.
.l-sub-section .l-sub-section
:marked :marked
@ -52,7 +52,7 @@ figure.image-display
block intro-file-tree block intro-file-tree
.filetree .filetree
.file angular2-tour-of-heroes .file angular-tour-of-heroes
.children .children
.file app .file app
.children .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. 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. 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 block angular-router
:marked :marked
@ -192,7 +192,7 @@ block router-config-intro
### Configure routes ### Configure routes
Our application doesn't have any routes yet. Our application doesn't have any routes yet.
We'll start by creating a configuration file for the application routes. We'll start by creating a configuration for the application routes.
:marked :marked
*Routes* tell the router which views to display when a user clicks a link or *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: 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') +makeExcerpt('app/' + _file + ' (heroes route)', 'heroes')
- var _are = _docsFor == 'dart' ? 'takes' : 'are' - var _are = _docsFor == 'dart' ? 'takes' : 'are'
@ -222,24 +222,18 @@ block router-config-intro
+ifDocsFor('ts|js') +ifDocsFor('ts|js')
:marked :marked
We'll export a `routing` constant initialized using the `RouterModule.forRoot` method applied to our !{_array} of routes. ### Make the router available
This method returns a **configured router module** that we'll add to our root NgModule, `AppModule`.
+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 .l-sub-section
:marked :marked
We call the `forRoot` method because we're providing a configured router at the _root_ of the application. 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. 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.
: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')
- var _heroesRoute = _docsFor == 'dart' ? "'Heroes'" : 'heroes' - var _heroesRoute = _docsFor == 'dart' ? "'Heroes'" : 'heroes'
:marked :marked
@ -317,12 +311,12 @@ block routerLink
Import the dashboard component and Import the dashboard component and
add the following route definition to the `!{_RoutesVsAtRouteConfig}` !{_array} of definitions. 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') +makeExcerpt(_file + ' (Dashboard route)', 'dashboard')
+ifDocsFor('ts|js') +ifDocsFor('ts|js')
:marked :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') +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 We can use a redirect route to make this happen. Add the following
to our array of route definitions: to our array of route definitions:
+makeExcerpt('app/app-routing.module.ts','redirect') +makeExcerpt('app/app.module.3.ts','redirect')
.l-sub-section .l-sub-section
:marked :marked
@ -354,7 +348,7 @@ block redirect-vs-use-as-default
.l-sub-section .l-sub-section
:marked :marked
We nestled 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. They don't do anything yet but they'll be convenient when we style the links a little later in the chapter.
:marked :marked
@ -367,30 +361,24 @@ block redirect-vs-use-as-default
Replace the `template` metadata with a `templateUrl` property that points to a new Replace the `template` metadata with a `templateUrl` property that points to a new
template file. template file.
+ifDocsFor('ts|js')
:marked
Set the `moduleId` property to `module.id` for module-relative loading of the `templateUrl`.
+makeExcerpt('app/dashboard.component.ts', 'templateUrl') +makeExcerpt('app/dashboard.component.ts', 'metadata')
.l-sub-section block templateUrl-path-resolution
block templateUrl-path-resolution //- N/A for TS
:marked
We specify the path _all the way back to the application root_ &mdash;
<span if-docs="ts">`app/` in this case &mdash;</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.
:marked :marked
Create that file with this content: Create that file with this content:
+makeExcerpt('app/dashboard.component.html') +makeExample('app/dashboard.component.1.html', '', 'app/dashboard.component.html')
:marked :marked
We use `*ngFor` once again to iterate over a list of heroes and display their names. 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. 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* ### Share the *HeroService*
We'd like to re-use the `HeroService` to populate the component's `heroes` !{_array}. 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. 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 :marked
Now implement the `DashboardComponent` class like this: Now implement the `DashboardComponent` class like this:
+makeExcerpt('app/dashboard.component.2.ts (class)', 'component') +makeExcerpt('app/dashboard.component.ts (class)', 'class')
:marked :marked
We've seen this kind of logic before in the `HeroesComponent`: 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. * 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. * 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) In this dashboard we cherry-pick four heroes (2nd, 3rd, 4th, and 5th)<span if-docs="ts"> with the `Array.slice` method</span>.
and stub the `gotoDetail` method until we're ready to implement it.
Refresh the browser and see four heroes in the new dashboard. 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. 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') +makeExcerpt(_file + ' (hero detail)','hero-detail')
:marked :marked
@ -521,7 +508,7 @@ block route-params
- var _ActivatedRoute = _docsFor == 'dart' ? 'RouteParams' : 'ActivatedRoute' - var _ActivatedRoute = _docsFor == 'dart' ? 'RouteParams' : 'ActivatedRoute'
:marked :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: into the constructor, saving their values in private fields:
+makeExcerpt('app/hero-detail.component.ts (constructor)', 'ctor') +makeExcerpt('app/hero-detail.component.ts (constructor)', 'ctor')
@ -534,7 +521,7 @@ block route-params
block ngOnInit block ngOnInit
:marked :marked
Inside the `ngOnInit` lifecycle hook, we use the `params` observable to 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 `ActivatedRoute` service
and use the `HeroService` to fetch the hero with that `id`. and use the `HeroService` to fetch the hero with that `id`.
+makeExcerpt('app/hero-detail.component.ts', 'ngOnInit') +makeExcerpt('app/hero-detail.component.ts', 'ngOnInit')
@ -567,7 +554,8 @@ block extract-id
How do we navigate somewhere else when we're done? 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. 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') +makeExcerpt('app/hero-detail.component.ts', 'goBack')
@ -586,16 +574,16 @@ block extract-id
+makeExcerpt('app/hero-detail.component.html', 'back-button', '') +makeExcerpt('app/hero-detail.component.html', 'back-button', '')
:marked :marked
Modifing the template to add this button spurs us to take one more Modifying the template to add this button spurs us to take one more
incremental improvement and migrate the template to its own file, incremental improvement and migrate the template to its own file,
called <span ngio-ex>hero-detail.component.html</span>: called <span ngio-ex>hero-detail.component.html</span>:
+makeExample('app/hero-detail.component.html') +makeExample('app/hero-detail.component.html')
:marked :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 :marked
Refresh the browser and see the results. 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. 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:
:marked +makeExample('app/dashboard.component.html', 'click', 'app/dashboard.component.html (repeated <a> tag)')
We stubbed the `gotoDetail` method when we rewrote the `DashboardComponent`.
Now we give it a real implementation.
+makeExcerpt('app/dashboard.component.ts','gotoDetail') +ifDocsFor('dart')
.alert.is-important
:marked
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' - var _pathVsName = _docsFor == 'dart' ? 'name' : 'path'
:marked :marked
The `gotoDetail` method navigates in two steps: Notice the `[routerLink]` binding.
1. Set a route *link parameters !{_array}* Top level navigation in the [`AppComponent`
1. Pass the !{_array} to the router's navigate method 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 This time, we're binding to an expression containing a **link parameters !{_array}**.
parameters !{_array}s*</span> in the [`AppComponent` The !{_array} has two elements, the ***!{_pathVsName}*** of
template](#router-links). Those link<span if-docs="dart"> parameters the destination route and a ***route parameter*** set to the value of the current hero's `id`.
!{_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`.
The two !{_array} items align with the ***!{_pathVsName}*** and ***:id*** The two !{_array} items align with the ***!{_pathVsName}*** and ***:id***
token in the parameterized hero detail route definition we added to token in the parameterized hero detail route definition we added to
`!{_appRoutingTsVsAppComp}` earlier in the chapter: `!{_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') +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 :marked
Refresh the browser and select a hero from the dashboard; the app should navigate directly to that heros details. Refresh the browser and select a hero from the dashboard; the app should navigate directly to that heros 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 .l-main-section
:marked :marked
## Select a Hero in the *HeroesComponent* ## 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`. 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. 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=".") +makeExample('toh-4/ts/app/app.component.ts','template', 'app/heroes.component.ts (current template)')(format=".")
:marked :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. Delete the last line of the template with the `<my-hero-detail>` tags.
We'll no longer show the full `HeroDetailComponent` here. 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. 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. 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 are keeping the "master/detail" style but shrinking the detail to a "mini", read-only version.
We'll show a *mini-detail* on *this* page instead and make the user click a button to navigate to the *full detail *page. 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* ### 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 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. *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. 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 .l-sub-section
:marked :marked
The `styleUrls` property is !{_an} !{_array} of style file names (with paths). 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. 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 block heroes-component-cleanup
//- Only relevant for Dart. //- Only relevant for Dart.
@ -728,15 +762,26 @@ block heroes-component-cleanup
+makeExcerpt('app/heroes.component.ts (revised metadata)', 'metadata') +makeExcerpt('app/heroes.component.ts (revised metadata)', 'metadata')
:marked :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. 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}**
&mdash; a path and the route parameter &mdash; 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') +makeExcerpt('app/heroes.component.ts', 'class')
@ -834,7 +879,7 @@ block css-files
+makeExcerpt('styles.css (excerpt)', 'toh') +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 :marked
Create the file <span ngio-ex>styles.css</span>, if it doesn't exist already. 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 block file-tree-end
.filetree .filetree
.file angular2-tour-of-heroes .file angular-tour-of-heroes
.children .children
.file app .file app
.children .children
@ -895,12 +940,13 @@ block file-tree-end
We travelled a great distance in this chapter 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 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 used router link parameters to navigate to the details of user selected hero.
- We shared the `HeroService` among multiple components. - We shared the `HeroService` among multiple components.
- We moved HTML and CSS out of the component file and into their own files. - We moved HTML and CSS out of the component file and into their own files.
- We added the `uppercase` pipe to format data. - 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 ### The Road Ahead

View File

@ -205,7 +205,6 @@ block router-config-intro
- var _are = _docsFor == 'dart' ? 'takes' : 'are' - var _are = _docsFor == 'dart' ? 'takes' : 'are'
- var _routePathPrefix = _docsFor == 'dart' ? '/' : '' - var _routePathPrefix = _docsFor == 'dart' ? '/' : ''
:marked :marked
The `!{_RoutesVsAtRouteConfig}` !{_are} !{_an} !{_array} of *route definitions*. The `!{_RoutesVsAtRouteConfig}` !{_are} !{_an} !{_array} of *route definitions*.
We have only one route definition at the moment but rest assured, we'll add more. We have only one route definition at the moment but rest assured, we'll add more.
@ -362,11 +361,15 @@ block redirect-vs-use-as-default
Replace the `template` metadata with a `templateUrl` property that points to a new Replace the `template` metadata with a `templateUrl` property that points to a new
template file. template file.
+ifDocsFor('ts|js')
Set the `moduleId` property to `module.id` for module-relative loading of the `templateUrl`. :marked
Set the `moduleId` property to `module.id` for module-relative loading of the `templateUrl`.
+makeExcerpt('app/dashboard.component.ts', 'metadata') +makeExcerpt('app/dashboard.component.ts', 'metadata')
block templateUrl-path-resolution
//- N/A for TS
:marked :marked
Create that file with this content: Create that file with this content:
@ -404,7 +407,7 @@ block redirect-vs-use-as-default
* Inject the `HeroService` in the constructor and hold it in a private `!{_priv}heroService` field. * 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. * Call the service to get heroes inside the Angular `ngOnInit` lifecycle hook.
In this dashboard we cherry-pick four heroes (2nd, 3rd, 4th, and 5th) with the `Array.slice` method. 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. Refresh the browser and see four heroes in the new dashboard.
@ -575,10 +578,10 @@ block extract-id
incremental improvement and migrate the template to its own file, incremental improvement and migrate the template to its own file,
called <span ngio-ex>hero-detail.component.html</span>: called <span ngio-ex>hero-detail.component.html</span>:
+makeExample('app/hero-detail.component.html')(format='.') +makeExample('app/hero-detail.component.html')
:marked :marked
We update the component metadata with a `moduleId` and 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', 'metadata') +makeExcerpt('app/hero-detail.component.ts', 'metadata')
@ -600,11 +603,17 @@ block extract-id
+makeExample('app/dashboard.component.html', 'click', 'app/dashboard.component.html (repeated <a> tag)') +makeExample('app/dashboard.component.html', 'click', 'app/dashboard.component.html (repeated <a> tag)')
+ifDocsFor('dart')
.alert.is-important
:marked
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' - var _pathVsName = _docsFor == 'dart' ? 'name' : 'path'
:marked :marked
Notice the `[routerLink]` binding. Notice the `[routerLink]` binding.
In the top level navigation in the [`AppComponent` Top level navigation in the [`AppComponent`
template](#router-links) has router links set to fixed !{_pathVsName}s of the template](#router-links) has router links set to fixed !{_pathVsName}s of the
destination routes, "/dashboard" and "/heroes". destination routes, "/dashboard" and "/heroes".
@ -622,53 +631,55 @@ block extract-id
:marked :marked
Refresh the browser and select a hero from the dashboard; the app should navigate directly to that heros details. Refresh the browser and select a hero from the dashboard; the app should navigate directly to that heros details.
.l-main-section +ifDocsFor('ts')
:marked .l-main-section
## Refactor routes to a _Routing Module_ :marked
## Refactor routes to a _Routing Module_
Almost 20 lines of `AppModule` are devoted to configuring four routes. Almost 20 lines of `AppModule` are devoted to configuring four routes.
Most application have many more routes and they [add guard services](../guide/router.html#guards) Most applications have many more routes and they [add guard services](../guide/router.html#guards)
to protect against unwanted or unauthorized navigations. to protect against unwanted or unauthorized navigations.
Routing considerations could quickly dominate this module and obscure its primary purpose which is to 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. establish key facts about the entire app for the Angular compiler.
We should refactor the routing configuration into its own class. We should refactor the routing configuration into its own class.
What kind of class? What kind of class?
The current `RouterModule.forRoot()` produces an Angular `ModuleWithProviders` which suggests that a The current `RouterModule.forRoot()` produces an Angular `ModuleWithProviders` which suggests that a
class dedicated to routing should be some kind of module. class dedicated to routing should be some kind of module.
It should be a [_Routing Module_](../guide/router.html#routing-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 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". 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') Create an `app-routing.module.ts` file as a sibling to `app.module.ts`. Give it the following contents extracted from the `AppModule` class:
: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`. +makeExample('app/app-routing.module.ts')
:marked
* Add `RouterModule` to `exports` so that the components in the companion module have access to Router declarables Noteworthy points, typical of _Routing Modules_:
such as `RouterLink` and `RouterOutlet`. * Pull the routes into a variable. You might export it in future and it clarifies the _Routing Module_ pattern.
* 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. * Add `RouterModule.forRoot(routes)` to `imports`.
### Update _AppModule_ * Add `RouterModule` to `exports` so that the components in the companion module have access to Router declarables
such as `RouterLink` and `RouterOutlet`.
Now delete the routing configuration from `AppModule` and import the `AppRoutingModule` * No `declarations`! Declarations are the responsibility of the companion module.
(_both_ with an ES `import` statement _and_ by adding it to the `NgModule.imports` list).
* 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.
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 .l-main-section
:marked :marked
## Select a Hero in the *HeroesComponent* ## Select a Hero in the *HeroesComponent*
@ -738,7 +749,7 @@ 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 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. *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. 1. *Set* the component metadata's `templateUrl` and `styleUrls` properties to refer to both files.
1. *Set* the `moduleId` property to `module.id` so that `templateUrl` and `styleUrls` are relative to the component. <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 .l-sub-section
:marked :marked
@ -764,11 +775,12 @@ block heroes-component-cleanup
1. Implement `gotoDetail` by calling the `router.navigate` method 1. Implement `gotoDetail` by calling the `router.navigate` method
+makeExcerpt('app/heroes.component.ts', 'gotoDetail') +makeExcerpt('app/heroes.component.ts', 'gotoDetail')
:marked :marked
Note that we're passing a two-element **link parameters !{_array}** Note that we're passing a two-element **link parameters !{_array}**
&mdash; a path and the route parameter &mdash; to &mdash; a path and the route parameter &mdash; to
the `router.navigate` method just as we did in the `[routerLink]` binding the `router.navigate` method just as we did in the `[routerLink]` binding
back in the `DashboardComponent` back in the `DashboardComponent`.
Here's the fully revised `HeroesComponent` class: Here's the fully revised `HeroesComponent` class:
+makeExcerpt('app/heroes.component.ts', 'class') +makeExcerpt('app/heroes.component.ts', 'class')
@ -934,7 +946,7 @@ block file-tree-end
- We shared the `HeroService` among multiple components. - We shared the `HeroService` among multiple components.
- We moved HTML and CSS out of the component file and into their own files. - We moved HTML and CSS out of the component file and into their own files.
- We added the `uppercase` pipe to format data. - We added the `uppercase` pipe to format data.
- We refactored routes into a `Routing Module` that we import. <li if-docs="ts"> We refactored routes into a `Routing Module` that we import.</li>
### The Road Ahead ### The Road Ahead

View File

@ -2,4 +2,3 @@
# <grep-pattern-to-match-file-path> # reason / issue number # <grep-pattern-to-match-file-path> # reason / issue number
/[jt]s/.*/api/forms/index/NG_VALIDATORS-let.html # RC6 contains broken example tags /[jt]s/.*/api/forms/index/NG_VALIDATORS-let.html # RC6 contains broken example tags
/dart/latest/tutorial/toh-pt5.html