parent
7e86e9411b
commit
e14f950a7c
|
@ -4,11 +4,11 @@
|
|||
import {Component, OnInit} from 'angular2/core';
|
||||
import {Crisis, CrisisService} from './crisis.service';
|
||||
import {RouteParams, Router} from 'angular2/router';
|
||||
// #docregion ngCanDeactivate
|
||||
// #docregion routerCanDeactivate
|
||||
import {CanDeactivate, ComponentInstruction} from 'angular2/router';
|
||||
import {DialogService} from '../dialog.service';
|
||||
|
||||
// #enddocregion ngCanDeactivate
|
||||
// #enddocregion routerCanDeactivate
|
||||
|
||||
@Component({
|
||||
// #docregion template
|
||||
|
@ -28,13 +28,13 @@ import {DialogService} from '../dialog.service';
|
|||
// #enddocregion template
|
||||
styles: ['input {width: 20em}']
|
||||
})
|
||||
// #docregion ngCanDeactivate, cancel-save
|
||||
// #docregion routerCanDeactivate, cancel-save
|
||||
export class CrisisDetailComponent implements OnInit, CanDeactivate {
|
||||
|
||||
public crisis: Crisis;
|
||||
public editName: string;
|
||||
|
||||
// #enddocregion ngCanDeactivate, cancel-save
|
||||
// #enddocregion routerCanDeactivate, cancel-save
|
||||
constructor(
|
||||
private _service: CrisisService,
|
||||
private _router: Router,
|
||||
|
@ -87,7 +87,7 @@ export class CrisisDetailComponent implements OnInit, CanDeactivate {
|
|||
this._router.navigate(route);
|
||||
}
|
||||
// #enddocregion gotoCrises
|
||||
// #docregion ngCanDeactivate, cancel-save
|
||||
// #docregion routerCanDeactivate, cancel-save
|
||||
}
|
||||
// #enddocregion ngCanDeactivate, cancel-save
|
||||
// #enddocregion routerCanDeactivate, cancel-save
|
||||
// #enddocregion
|
||||
|
|
|
@ -403,7 +403,7 @@ figure
|
|||
Here's an example of a service class that logs to the browser console
|
||||
+makeExample('architecture/ts/app/logger.service.ts', 'class', 'app/logger.service.ts (class only)')(format=".")
|
||||
:marked
|
||||
Here's a `HeroServce` that fetches heroes and returns them in a resolved [promise](http://www.html5rocks.com/en/tutorials/es6/promises/).
|
||||
Here's a `HeroService` that fetches heroes and returns them in a resolved [promise](http://www.html5rocks.com/en/tutorials/es6/promises/).
|
||||
The `HeroService` depends on the `LoggerService` and another `BackendService` that handles the server communication grunt work.
|
||||
+makeExample('architecture/ts/app/hero.service.ts', 'class', 'app/hero.service.ts (class only)')(format=".")
|
||||
:marked
|
||||
|
|
|
@ -53,9 +53,8 @@ include ../../../../_includes/_util-fns
|
|||
:marked
|
||||
Now we know how the router gets its configuration.
|
||||
When the browser URL for this application becomes `/heroes`,
|
||||
the router finds the `RouteDefintion` named *Heroes* and then knows to display the `HeroListComponent`.
|
||||
|
||||
Display it where? It will display in a **`RouterOutlet`** that we've placed in the host view's HTML.
|
||||
the router matches that URL to the `RouteDefintion` named *Heroes* and displays the `HeroListComponent`
|
||||
in a **`RouterOutlet`** that we've placed in the host view's HTML.
|
||||
code-example(format="", language="html").
|
||||
<!-- Routed views go here -->
|
||||
<router-outlet></router-outlet>
|
||||
|
@ -65,8 +64,8 @@ code-example(format="", language="html").
|
|||
But most of the time we navigate as a result of some user action such as the click of
|
||||
an anchor tag.
|
||||
|
||||
In an anchor tag we bind a **`RouterLink`** Directive to a template expression that
|
||||
returns an **array of route link parameters**. The router ultimately resolves that array
|
||||
We add a **`RouterLink`** directive to the anchor tag and bind it to a template expression that
|
||||
returns an array of route link parameters (the **link parameters array**). The router ultimately resolves that array
|
||||
into a URL and a component view.
|
||||
|
||||
We see such bindings in the following `AppComponent` template:
|
||||
|
@ -77,7 +76,8 @@ code-example(format="", language="html").
|
|||
We bind each `RouterLink` to an array containing the string name of a route definition.
|
||||
'CrisisCenter' and 'Heroes' are the names of the `Routes` we configured above.
|
||||
|
||||
We'll learn to write more complex link expressions ... and why they are arrays ... later in the chapter.
|
||||
We'll learn to write more complex link expressions — and why they are arrays —
|
||||
[later](#link-parameter-array) in the chapter.
|
||||
:marked
|
||||
### Let's summarize
|
||||
|
||||
|
@ -126,31 +126,38 @@ table
|
|||
An array that the router inteprets into a routing instruction.
|
||||
We can bind a <code>RouterLink</code> to that array or pass the array as an argument to
|
||||
the <code>Router.navigate</code> method.
|
||||
tr
|
||||
td <i>Routing Component</i></code>
|
||||
td.
|
||||
An Angular component with an attached router.
|
||||
:marked
|
||||
We'll learn many more details in this chapter which covers
|
||||
|
||||
* [configuring a router](#route-config)
|
||||
* the [link parameter arrays](#link-parameters-array) that propel router navigation
|
||||
* navigating when the user clicks a data-bound ['RouterLink'](#router-link)
|
||||
* navigating when the user clicks a data-bound [RouterLink](#router-link)
|
||||
* navigating under [program control](#navigate)
|
||||
* passing information in [route parameters](#route-parameter)
|
||||
* creating a [child router](#child-router) with its own routes
|
||||
* setting a [default route](#default)
|
||||
* pausing, confirming and/or canceling a navigation with the the 'CanDeactivate' [lifecycle hook](#lifecycle-hooks)
|
||||
* pausing, confirming and/or canceling a navigation with the the `routerCanDeactivate` [router lifecycle hook](#lifecycle-hooks)
|
||||
|
||||
We will proceed in phases marked by milestones.
|
||||
Our first milestone is the ability to navigate between between two placeholder views.
|
||||
We proceed in phases marked by milestones.
|
||||
Our first milestone is the ability to navigate between two placeholder views.
|
||||
At our last milestone, we'll have a modular, multi-view design with child routes.
|
||||
|
||||
We assume that you're already comfortable with the basic Angular 2 concepts and tools
|
||||
We assume that you're already comfortable with the basic Angular 2 tools and concepts
|
||||
we introduced in the [QuickStart](../quickstart.html) and
|
||||
the [Tour of Heroes](../tutorial/) tutorial.
|
||||
.l-sub-section
|
||||
:marked
|
||||
While we make incremental progress on a sample application, this chapter is not a tutorial.
|
||||
We discuss code and design decisions pertinent to routing and application design.
|
||||
We gloss over everything in between.
|
||||
|
||||
The full source is available in the [live example](/resources/live-examples/router/ts/plnkr.html).
|
||||
:marked
|
||||
|
||||
While there is a progression, this chapter is not a tutorial.
|
||||
We discuss code and design decisions pertinent to routing and application design.
|
||||
We gloss over everything else.
|
||||
|
||||
The full source is available in the [live example](/resources/live-examples/router/ts/plnkr.html).
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
|
@ -189,22 +196,24 @@ figure.image-display
|
|||
figure.image-display
|
||||
img(src='/resources/images/devguide/router/crisis-center-detail.png' alt="Crisis Center Detail")
|
||||
:marked
|
||||
This is a bit different then the "Heroes Detail". We have two buttons, "Save" and "Cancel".
|
||||
If we make a change and click "Save", we return to the *Crisis Center* and see our changes
|
||||
reflected in the list. If we make a change and click "Cancel",
|
||||
we return to the *Crisis Center* but this time our changes were discarded.
|
||||
This is a bit different from the *Hero Detail*. *Hero Detail* saves the changes as we type.
|
||||
In *Crisis Detail* our changes are temporary until we either save or discard them
|
||||
with by pressing the "Save" or "Cancel" buttons.
|
||||
Both buttons navigate back to the *Crisis Center* and its list of crises.
|
||||
|
||||
Now we click a crisis, make a change, and ***do not click either button***.
|
||||
We click the browser back button instead. Up pops a modal dialog box.
|
||||
Suppose we click a crisis, make a change, but ***do not click either button***.
|
||||
Maye we click the browser back button instead. Maybe we click the "Heroes" link.
|
||||
|
||||
Do either. Up pops a dialog box.
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/router/confirm-dialog.png' alt="Confirm Dialog" width="300")
|
||||
:marked
|
||||
We can say "OK" and lose our changes or click "Cancel" and continue editing.
|
||||
|
||||
The router supports a `CanDeactivate` lifecycle" method that gives us a chance to clean-up
|
||||
The router supports a `routerCanDeactivate` lifecycle hook that gives us a chance to clean-up
|
||||
or ask the user's permission before navigating away from the current view.
|
||||
|
||||
Let's see a quick demonstration of the workflow in action.
|
||||
Here we see an entire user session that touches all of these features.
|
||||
<a id="full-app-demo"></a>
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/router/router-anim.gif' alt="App in action" )
|
||||
|
@ -218,7 +227,7 @@ figure.image-display
|
|||
* navigating to a component (*Heroes* link to "Heroes List")
|
||||
* including a route parameter (passing the Hero `id` while routing to the "Hero Detail")
|
||||
* child routes (the *Crisis Center* has its own routes)
|
||||
* the `CanDeactivate` lifecycle method (ask before discarding changes)
|
||||
* the `routerCanDeactivate` lifecycle hook (ask permission to discard unsaved changes)
|
||||
|
||||
<a id="getting-started"></a>
|
||||
.l-main-section
|
||||
|
@ -230,7 +239,7 @@ figure.image-display
|
|||
img(src='/resources/images/devguide/router/router-1-anim.gif' alt="App in action" )
|
||||
:marked
|
||||
### Load the Component Router library
|
||||
The Component Router is not part of the Angular 2 core. It is its own library.
|
||||
The Component Router is not part of the Angular 2 core. It is in its own library.
|
||||
The router is an optional service and you might prefer a different router someday.
|
||||
|
||||
The Component Router library is part of the Angular npm bundle.
|
||||
|
@ -282,21 +291,21 @@ figure.image-display
|
|||
:marked
|
||||
We import our root `AppComponent` and Angular's `bootstrap` function as expected.
|
||||
|
||||
We also import `ROUTER_PROVIDERS` from Dependency Injection.
|
||||
The router is a service implemented by a collection of providers, most of which are identified in the
|
||||
We also import `ROUTER_PROVIDERS` from the router library.
|
||||
The router is a service implemented by a collection of *Dependency Injection* providers, most of which are identified in the
|
||||
`ROUTER_PROVIDERS` array.
|
||||
|
||||
As usual, we're booting Angular with `AppComponent` as our app's root component and
|
||||
registering providers in an array in the second parameter of the `bootstrap` function.
|
||||
Providing the router providers at the root makes the router available everywhere in our application.
|
||||
We're booting Angular with `AppComponent` as our app's root component and
|
||||
registering providers, as we often do, in the providers array in the second parameter of the `bootstrap` function.
|
||||
Providing the router providers at the root makes the Component Router available everywhere in our application.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn about providers, the `provide` function, and injected services in the
|
||||
[Dependency Injection chapter](dependency-injection.html).
|
||||
:marked
|
||||
### The *AppComponent* shell
|
||||
The root `AppComponent` is the shell of our application. It has title at the top, a navigation bar with two links,
|
||||
and a "router outlet" below where the router swaps views on and off the page. Here's what we mean:
|
||||
The root `AppComponent` is the application shell. It has title at the top, a navigation bar with two links,
|
||||
and a *Router Outlet* at the bottom where the router swaps views on and off the page. Here's what we mean:
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/router/shell-and-outlet.png' alt="Shell" width="300" )
|
||||
:marked
|
||||
|
@ -316,7 +325,7 @@ figure.image-display
|
|||
:marked
|
||||
### *RouterLink* binding
|
||||
Above the outlet, within the anchor tags, we see [Property Bindings](template-syntax.html#property-binding) to
|
||||
the `RouterLink` Directive that look like `[routerLink]="[...]"`. We imported `RouterLink` from the router library.
|
||||
the `RouterLink` directive that look like `[routerLink]="[...]"`. We imported `RouterLink` from the router library.
|
||||
|
||||
The template expression to the right of the equals (=) returns a *link parameters array*.
|
||||
|
||||
|
@ -356,7 +365,7 @@ figure.image-display
|
|||
1. the browser URL changes
|
||||
2. we tell the router to go to a named route
|
||||
|
||||
Translating these two definitions into English, we might say:
|
||||
In plain English, we might say of the first route:
|
||||
1. *When the browser's location URL changes to **match the path** segment `/crisis-center`, create or retrieve an instance of
|
||||
the `CrisisCenterComponent` and display its view.*
|
||||
|
||||
|
@ -377,7 +386,7 @@ figure.image-display
|
|||
* set the router to compose "HTML 5" browser URLs.
|
||||
|
||||
The rest of the starter app is mundane, with little interest from a router perspective.
|
||||
Here are the details for readers inclined to build the sample through this milestone.
|
||||
Here are the details for readers inclined to build the sample through to this milestone.
|
||||
|
||||
Our starter app's structure looks like this:
|
||||
.filetree
|
||||
|
@ -414,25 +423,25 @@ figure.image-display
|
|||
We've seen how to navigate using the `RouterLink` directive.
|
||||
|
||||
Now we'll learn some new tricks such as how to
|
||||
* organize our app into "feature areas"
|
||||
* organize our app into *feature areas*
|
||||
* navigate imperatively from one component to another
|
||||
* pass information along in route parameters (`RouteParams`)
|
||||
|
||||
To demonstrate all of this we'll build out the *Heroes* feature.
|
||||
To demonstrate, we'll build out the *Heroes* feature.
|
||||
|
||||
### The Heroes "feature area"
|
||||
|
||||
A typical application has multiple "feature areas", each an island of functionality
|
||||
dedicated to an area of interest with its own workflow(s).
|
||||
A typical application has multiple *feature areas*, each an island of functionality
|
||||
with its own workflow(s), dedicated to a particular business purpose.
|
||||
|
||||
We could continue to add files to the `app/` folder.
|
||||
That's unrealistic and ultimately not maintainable.
|
||||
We think it's best if each feature area is in its own folder.
|
||||
We think it's better to put each feature area in its own folder.
|
||||
|
||||
Our first step is to **create a separate `app/heroes/` folder**.
|
||||
Then we'll add Hero management feature files.
|
||||
and add *Hero Management* feature files there.
|
||||
|
||||
We won't be creative about this. Our example is pretty much a
|
||||
We won't be creative about it. Our example is pretty much a
|
||||
copy of the code and capabilities in the "[Tutorial: Tour of Heroes](../tutorial/index.html)".
|
||||
|
||||
Here's how the user will experience this version of the app
|
||||
|
@ -447,9 +456,9 @@ figure.image-display
|
|||
We create a new `hero-list.component.ts` in the `app/heroes/`
|
||||
folder and copy over the contents of the final `heroes.component.ts` from the tutorial.
|
||||
We also copy the `hero-detail.component.ts` and the `hero.service.ts` files
|
||||
into the `heroes/` folder while we're at it.
|
||||
into the `heroes/` folder.
|
||||
|
||||
When were done organizing, we have three "Hero" files:
|
||||
When were done organizing, we have three *Hero Management* files:
|
||||
|
||||
.filetree
|
||||
.file app/heroes
|
||||
|
@ -458,8 +467,8 @@ figure.image-display
|
|||
.file hero-list.component.ts
|
||||
.file hero.service.ts
|
||||
:marked
|
||||
Here as in the tutorial, we'll provide the `HeroService` during bootstrapping
|
||||
so that is available anywhere in the app (see `boot.ts`) .
|
||||
We'll provide the `HeroService` during bootstrapping
|
||||
so that is available everywhere in the app (see `boot.ts`) .
|
||||
|
||||
Now it's time for some surgery to bring these files and the rest of the app
|
||||
into alignment with our application router.
|
||||
|
@ -488,18 +497,18 @@ figure.image-display
|
|||
While we moved `hero-list.component.ts` to a new location in the `app/heroes/` folder, that only affects the `import` statement;
|
||||
it doesn't affect its route definition.
|
||||
|
||||
We added a new route definition for the `HeroDetailComponent` ... and this definition has a twist.
|
||||
We added a new route definition for the `HeroDetailComponent` — and this definition has a twist.
|
||||
+makeExample('router/ts/app/app.component.2.ts','hero-detail-route')(format=".")
|
||||
:marked
|
||||
Notice the `:id` token in the the path. That creates a slot in the path for a **Route Parameter**.
|
||||
In this case, we're expecting the router to insert the `id` of a hero into that slot.
|
||||
|
||||
If we tell the router to navigate to the detail component and display "Magenta", we expect her `id` (15) to appear in the
|
||||
If we tell the router to navigate to the detail component and display "Magenta", we expect hero `id` (15) to appear in the
|
||||
browser URL like this:
|
||||
code-example(format="." language="bash").
|
||||
localhost:3000/hero/15
|
||||
:marked
|
||||
If someone enters that URL into the browser address bar, the router should recognize the
|
||||
If a user enters that URL into the browser address bar, the router should recognize the
|
||||
pattern and go to the same "Magenta" detail view.
|
||||
|
||||
<a id="navigate"></id>
|
||||
|
@ -511,22 +520,21 @@ code-example(format="." language="bash").
|
|||
Instead, we'll *detect* when the user selects a hero from the list and *command* the router
|
||||
to present the hero detail view of the selected hero.
|
||||
|
||||
We'll adjust the `HeroListComponent` to implement these tasks beginning with its template:
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.ts','template')
|
||||
:marked
|
||||
The template defines an `*ngFor` repeater such as [we've seen before](displaying-data.html#ngFor).
|
||||
There's a `(click)` [EventBinding](template-syntax.html#event-binding) to the component's `select` method.
|
||||
|
||||
The `select` method will call the router service which we acquire by dependency injection
|
||||
(along with the `HeroService` that gives us heroes to show):
|
||||
We'll adjust the `HeroListComponent` to implement these tasks, beginning with its constructor
|
||||
which acquires the router service and the `HeroService` by dependency injection:
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.ts','ctor')(format=".")
|
||||
:marked
|
||||
Here's the `select` method:
|
||||
We make a few changes to the template:
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.ts','template')(format=".")
|
||||
:marked
|
||||
The template defines an `*ngFor` repeater such as [we've seen before](displaying-data.html#ngFor).
|
||||
There's a `(click)` [EventBinding](template-syntax.html#event-binding) to the component's `onSelect` method
|
||||
which we implement as follows:
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.ts','select')(format=".")
|
||||
:marked
|
||||
It calls the router's **`navigate`** method with a **Link Parameters Array**.
|
||||
This one is similar to the *link parameters array* we met [earlier](#shell-template) in an anchor tag,
|
||||
binding to the `RouterLink` directive, only this time we're seeing it in code rather than in HTML.
|
||||
This array is similar to the *link parameters array* we met [earlier](#shell-template) in an anchor tag while
|
||||
binding to the `RouterLink` directive. This time we see it in code rather than in HTML.
|
||||
<a id="route-parameter"></id>
|
||||
### Setting the route parameter
|
||||
|
||||
|
@ -537,14 +545,14 @@ code-example(format="." language="bash").
|
|||
`id` of the selected hero.
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.ts','link-parameters-array')(format=".")
|
||||
:marked
|
||||
The router composes the appropriate two-part destination URL:
|
||||
The router composes the appropriate two-part destination URL from this array:
|
||||
code-example(format="." language="bash").
|
||||
localhost:3000/hero/15
|
||||
:marked
|
||||
### Getting the route parameter
|
||||
|
||||
<a id="hero-detail-ctor"></a>
|
||||
How does the target `HeroDetailComponent` get that `id`?
|
||||
How does the target `HeroDetailComponent` learn about that `id`?
|
||||
Certainly not by analyzing the URL! That's the router's job.
|
||||
|
||||
The router extracts the route parameter (`id:15`) from the URL and supplies it to
|
||||
|
@ -571,8 +579,9 @@ code-example(format="." language="bash").
|
|||
The `HeroDetailComponent` has a "Back" button wired to its `gotoHeroes` method that navigates imperatively
|
||||
back to the `HeroListComponent`.
|
||||
|
||||
The router `navigate` method takes the same, one-item *link parameters array*, holding
|
||||
the **name of the `HeroListComponent` route**, that we used in the `[routerLink]` directive binding.
|
||||
The router `navigate` method takes the same one-item *link parameters array*
|
||||
that we wrote for the `[routerLink]` directive binding.
|
||||
It holds the **name of the `HeroListComponent` route**:
|
||||
+makeExample('router/ts/app/heroes/hero-detail.component.ts','gotoHeroes')(format=".")
|
||||
:marked
|
||||
### Heroes App Wrap-up
|
||||
|
@ -580,7 +589,7 @@ code-example(format="." language="bash").
|
|||
We've reached the second milestone in our router education.
|
||||
|
||||
We've learned how to
|
||||
* organize our app into "feature areas"
|
||||
* organize our app into *feature areas*
|
||||
* navigate imperatively from one component to another
|
||||
* pass information along in route parameters (`RouteParams`)
|
||||
|
||||
|
@ -638,10 +647,8 @@ code-example(format="." language="bash").
|
|||
|
||||
Voilà, instant feature module!
|
||||
|
||||
Of course this is only a sample application.
|
||||
There's no point to this exercise unless we can learn something new.
|
||||
|
||||
We do have new points to make:
|
||||
There's no point to this exercise unless we can learn something.
|
||||
We do have new ideas and techniques in mind:
|
||||
|
||||
* The application should navigate to the *Crisis Center* by default.
|
||||
|
||||
|
@ -665,8 +672,8 @@ code-example(format="." language="bash").
|
|||
|
||||
We'll fix all of these problems and add the new routing features to *Crisis Center*.
|
||||
|
||||
The most important fix, from a router perspective, is the introduction of a **child *Routing Component***
|
||||
with its **child router**
|
||||
The most significant fix is the introduction of a **child *Routing Component***
|
||||
and its **child router**
|
||||
|
||||
We'll leave *Heroes* in its less-than-perfect state to
|
||||
serve as a contrast with what we hope is a superior *Crisis Center*.
|
||||
|
@ -678,8 +685,8 @@ code-example(format="." language="bash").
|
|||
The `CrisisCenter` and `Heroes` components are children of the root `AppComponent`.
|
||||
|
||||
Unfortunately, they and their related files are physically commingled in the same folder with the `AppComponent`.
|
||||
We'd prefer to separate them in their own "feature areas" so they can operate and evolve independently.
|
||||
Someday we might re-use one or the other of them in a different application.
|
||||
We'd prefer to separate them in their own *feature areas* so they can operate and evolve independently.
|
||||
Someday we might re-use one or the other in a different application.
|
||||
Someday we might load one of them dynamically only when the user chose to enter its workflow.
|
||||
|
||||
Some might call it [yagni](http://martinfowler.com/bliki/Yagni.html) to even think about such things.
|
||||
|
@ -718,13 +725,12 @@ code-example(format="." language="bash").
|
|||
just as the `AppComponent` is a shell to manage the high-level workflow.
|
||||
|
||||
`AppComponent` has a `@RouteConfig` decorator that defines the top-level routes.
|
||||
`CrisisCenterComponent` has a `@RouteConfig` decorator that defines *Crisis Center* routes.
|
||||
The two sets of routes *do not overlap*.
|
||||
`CrisisCenterComponent` has a `@RouteConfig` decorator that defines *Crisis Center* child routes.
|
||||
|
||||
`CrisisCenterComponent` template is dead simple — simpler even than the `AppComponent` template.
|
||||
It has no content, no links, just a `<router-outlet>` for the *Crisis Center* views.
|
||||
The `CrisisCenterComponent` template is dead simple — simpler even than the `AppComponent` template.
|
||||
It has no content, no links, just a `<router-outlet>` for the *Crisis Center* child views.
|
||||
|
||||
It has no selector either. It doesn't need one. We don't *embed* this component in a parent template. We navigate to it
|
||||
It has no selector either. It doesn't need one. We don't *embed* this component in a parent template. We *navigate* to it
|
||||
from the outside, via a parent router (more on that soon).
|
||||
|
||||
### Service isolation
|
||||
|
@ -742,7 +748,7 @@ code-example(format="." language="bash").
|
|||
|
||||
The `@RouteConfig` decorator that adorns the `CrisisCenterComponent` class defines routes in the same way
|
||||
that we did earlier.
|
||||
+makeExample('router/ts/app/crisis-center/crisis-center.component.ts', 'route-config', 'app/crisis-center/crisis-center.component.ts (routes only)' )
|
||||
+makeExample('router/ts/app/crisis-center/crisis-center.component.ts', 'route-config', 'app/crisis-center/crisis-center.component.ts (routes only)' )(format=".")
|
||||
:marked
|
||||
There are three *Crisis Center* routes, two of them with an `id` parameter.
|
||||
They refer to components we haven't talked about yet but whose purpose we
|
||||
|
@ -751,12 +757,11 @@ code-example(format="." language="bash").
|
|||
We cannot tell by looking at the `CrisisCenterComponent` that it is a child component
|
||||
of an application. We can't tell that its routes are child routes.
|
||||
|
||||
That's entirely deliberate. The *Crisis Center* shouldn't know that it is the child of anything.
|
||||
It might be the root of its own application. It might be repurposed in a different application.
|
||||
The *Crisis Center* can be indifferent.
|
||||
That's intentional. The *Crisis Center* shouldn't know that it is the child of anything.
|
||||
It might be the top level component of its own application. It might be repurposed in a different application.
|
||||
The *Crisis Center* itself is indifferent to these possibilities.
|
||||
|
||||
*We know* that it is child component in our application because we re-configured the
|
||||
routes of the top-level `AppComponent` to make it so.
|
||||
*We* make it a child component of our application by reconfiguring the routes of the top level `AppComponent`.
|
||||
:marked
|
||||
### Parent Route Configuration
|
||||
Here is is the revised route configuration for the parent `AppComponent`:
|
||||
|
@ -768,11 +773,11 @@ code-example(format="." language="bash").
|
|||
:marked
|
||||
Notice that the **path ends with a slash and three trailing periods (`/...`)**.
|
||||
|
||||
That means this is an incomplete route (AKA a ***non-terminal route***). The finished route will include the
|
||||
contribution of a **child router**, the router attached to the designated component which, perforce, must be a *Routing Component*.
|
||||
That means this is an incomplete route (AKA a ***non-terminal route***). The finished route will be some combination of
|
||||
the parent `/crisis-center/` route and a route from the **child router** that belongs to the designated component.
|
||||
|
||||
All is well.
|
||||
As we know, the route's component is the `CrisisCenterComponent` with its own router and routes.
|
||||
The parent route's designated component is the `CrisisCenterComponent` which is a *Routing Component* with its own router and routes.
|
||||
|
||||
<a id="default"></a>
|
||||
### Default route
|
||||
|
@ -783,57 +788,60 @@ code-example(format="." language="bash").
|
|||
|
||||
### Routing to the Child
|
||||
|
||||
We've set the default route to go to the `CrisisCenterComponent`. We learned the this default route is incomplete.
|
||||
The final route is a combination of the default route's `/crisis-center/` path fragment and one of the child `CrisisCenterComponent`
|
||||
router's *three* routes. Which one?
|
||||
We've set the top level default route to go to the `CrisisCenterComponent`.
|
||||
The final route will be a combination of `/crisis-center/`
|
||||
and one of the child `CrisisCenterComponent` router's *three* routes. Which one?
|
||||
|
||||
It could be any of three. In the absence of additional information, the router can't decide and must throw an error.
|
||||
Our sample application didn't fail. We must have done something.
|
||||
It could be any of the three. In the absence of additional information, the router can't decide and must throw an error.
|
||||
|
||||
We've tried the sample application and it didn't fail. We must have done something.
|
||||
|
||||
Scroll to the end of the `CrisisCenterComponent`s first route.
|
||||
Scroll to the end of the child `CrisisCenterComponent`s first route.
|
||||
+makeExample('router/ts/app/crisis-center/crisis-center.component.ts', 'default-route', 'app/crisis-center/crisis-center.component.ts (default route)')(format=".")
|
||||
:marked
|
||||
There is `useAsDefault: true` again. That tells the router to compose the final URL using the default child route.
|
||||
The result is:
|
||||
There is `useAsDefault: true` again. That tells the router to compose the final URL using the path from the default child route.
|
||||
Concatenate the base URL with `/crisis-center/` and `/`, remove extraneous slashes, and we get:
|
||||
code-example(format="").
|
||||
localhost:3000//crisis-center/
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
### Handling Unsaved Changes
|
||||
Back in the "Heroes" workflow, every change to a Hero is accepted immediately without any validation.
|
||||
Back in the "Heroes" workflow, the app accepts every change to a hero immediately without hesitation or validation.
|
||||
In the real world, we might have to accumulate the users changes.
|
||||
We might have to validate across fields. We might have to validate on the server.
|
||||
We might have to hold changes in a pending state until the user confirms them all at once or
|
||||
cancels and reverts.
|
||||
We might have to hold changes in a pending state until the user confirms them *as a group* or
|
||||
cancels and reverts all changes.
|
||||
|
||||
What do we do about unapproved, unsaved changes when the user navigates away?
|
||||
We'd like to pause and let the user decide what to do. Perhaps we'll cancel the
|
||||
navigation, stay put, and make more changes.
|
||||
|
||||
We need the router's cooperation to pull this off. We need lifecycle hooks.
|
||||
We need the router's cooperation to pull this off. We need router lifecycle hooks.
|
||||
|
||||
<a id="lifecycle-hooks"></a>
|
||||
### Router Lifecycle Hooks
|
||||
Angular components have their own [lifecycle hooks](lifecycle-hooks.html). Angular calls the methods of the
|
||||
Angular components have [lifecycle hooks](lifecycle-hooks.html). For example, Angular calls the hook methods of the
|
||||
[OnInit](../api/core/OnInit-interface.html) and [OnDestroy]((../api/core/OnDestroy-interface.html)
|
||||
interfaces when it creates and destroys components.
|
||||
|
||||
The router calls similar hook methods,
|
||||
[canActivate](../api/router/CanActivate-var.html) and [canDeactivate](../api/router/CanDeactivate-interface.html),
|
||||
when it is *about* to navigate to a component and when it is *about* to navigate away.
|
||||
[routerCanActivate](../api/router/CanActivate-var.html) and [routerCanDeactivate](../api/router/CanDeactivate-interface.html),
|
||||
before it navigates *to* a component or *away* from a component.
|
||||
|
||||
If a *`can...`* method returns `true`, the navigation proceeds. If it returns `false`, the
|
||||
router cancels the navigation and stays on the current view.
|
||||
|
||||
There is a important difference between the router lifecycle hooks and the component hooks. The component hooks are synchronous.
|
||||
The component hooks are synchronous and they can't stop creation or stop destruction!
|
||||
|
||||
That won't do for view navigation.
|
||||
The router lifecycle hooks *supplement* the component lifecycle hooks.
|
||||
We still need the component hooks but the router hooks do what the component hooks cannot.
|
||||
|
||||
For example, the component hooks can't stop component creation or destruction.
|
||||
Because they are synchronous, they can't pause view navigation to wait for an asynchronous process to finish.
|
||||
|
||||
Imagine we have unsaved changes. The user starts to navigate away.
|
||||
We can't lose the users changes. So we try to save those changes to the server.
|
||||
If the save fails for any reason (perhaps the data are invalid), what do we do?
|
||||
We shouldn't lose the user's changes; that would be a terrible experience. So we try to save those changes to the server.
|
||||
|
||||
If the save fails for any reason (perhaps the data are ruled invalid), what do we do?
|
||||
|
||||
If we let the user move to the next screen, we have lost the context of the error.
|
||||
We can't block while waiting for the server — that's not possible in a browser.
|
||||
|
@ -841,54 +849,55 @@ code-example(format="").
|
|||
We need to stop the navigation while we wait, asynchronously, for the server
|
||||
to return with its answer.
|
||||
|
||||
Fortunately, the router hook methods can be asynchronous and support promised.
|
||||
The router hook methods can pause asynchronously, return promises, and cancel navigation if necessary.
|
||||
|
||||
### Cancel and Save
|
||||
|
||||
Our sample application doesn't talk to a server.
|
||||
We can demonstrate an asynchronous router hook with a simulation.
|
||||
Our sample application doesn't talk to a server.
|
||||
Fortunately, we have another way to demonstrate an asynchronous router hook.
|
||||
|
||||
Users update crisis information in the `CrisisDetailComponent`.
|
||||
Unlike the `HeroDetailComponent`, user changes do not update the
|
||||
crisis entity until the user presses the *Save* button.
|
||||
Unlike the `HeroDetailComponent`, the user changes do not update the
|
||||
crisis entity immediately. We update the entity when the user presses the *Save* button.
|
||||
We discard the changes if the user presses he *Cancel* button.
|
||||
|
||||
Alternatively, the user can press the *Cancel* button to discard the changes.
|
||||
|
||||
Both buttons navigate back to the crisis list after saving or reverting.
|
||||
Both buttons navigate back to the crisis list after save or cancel.
|
||||
+makeExample('router/ts/app/crisis-center/crisis-detail.component.ts', 'cancel-save', 'crisis-detail.component.ts (excerpt)')(format=".")
|
||||
:marked
|
||||
But what if the user attempts to navigate away before saving or canceling?
|
||||
What if the user tries to navigate away without saving or canceling?
|
||||
The user could push the browser back button or click the heroes link.
|
||||
Both actions trigger a navigation.
|
||||
Should the app save or revert automatically?
|
||||
Should the app save or cancel automatically?
|
||||
|
||||
We'll do neither. Instead we'll ask the user to make that choice ...
|
||||
in a confirmation dialog service that *waits asynchronously for the user's
|
||||
We'll do neither. Instead we'll ask the user to make that choice explicitly
|
||||
in a confirmation dialog box that *waits asynchronously for the user's
|
||||
answer*.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Waiting for the user's answer could be handled with synchronous blocking code.
|
||||
But that the app will be more responsive ... and can do other work ...
|
||||
if we wait for the user's answer asynchronous. Waiting for asynchronously for the user
|
||||
is like waiting asynchronously for the server.
|
||||
We could wait for the user's answer with synchronous, blocking code.
|
||||
Our app will be more responsive ... and can do other work ...
|
||||
by waiting for the user's answer asynchronously. Waiting for the user asynchronously
|
||||
is like waiting for the server asynchronously.
|
||||
:marked
|
||||
The dialog service returns a [promise](http://www.html5rocks.com/en/tutorials/es6/promises/).
|
||||
The promise *resolves* when the user eventually decides
|
||||
to discard changes (`true`) or stay in the crisis editor (`false`).
|
||||
to discard changes and navigate away (`true`) or keep the pending changes and stay in the crisis editor (`false`).
|
||||
|
||||
<a id="canDeactivate"></a>
|
||||
<a id="CanDeactivate"></a>
|
||||
<a id="routerCanDeactivate"></a>
|
||||
:marked
|
||||
We execute the dialog inside the router's `routerCanDeactivate` lifecycle hook method.
|
||||
+makeExample('router/ts/app/crisis-center/crisis-detail.component.ts', 'canDeactivate', 'crisis-detail.component.ts (excerpt)')
|
||||
+makeExample('router/ts/app/crisis-center/crisis-detail.component.ts', 'routerCanDeactivate', 'crisis-detail.component.ts (excerpt)')
|
||||
:marked
|
||||
Notice that the `routerCanDeactivate` method *can* return synchronously.
|
||||
Notice that the `routerCanDeactivate` method *can* return synchronously;
|
||||
it returns `true` immediately if there are no pending changes.
|
||||
But it can also return a promise and the router will wait for that promise
|
||||
to resolve before navigating away or staying put.
|
||||
|
||||
**Two critical points**
|
||||
1. The method is optional. We don't inherit from a base class. We simply implement it or not.
|
||||
1. The router hook is optional. We don't inherit from a base class. We simply implement the method or not.
|
||||
|
||||
1. We rely on the router to call this hook. We don't worry about all the ways that the user
|
||||
1. We rely on the router to call the hook. We don't worry about all the ways that the user
|
||||
could navigate away. That's the router's job.
|
||||
We simply write this method and let the router take it from there.
|
||||
|
||||
|
@ -896,7 +905,7 @@ code-example(format="").
|
|||
.l-main-section
|
||||
:marked
|
||||
## Wrap Up
|
||||
As we end our chapter together, we take a parting look at
|
||||
As we end our chapter, we take a parting look at
|
||||
the entire application.
|
||||
|
||||
We can always try the [live example](../resources/live-examples/router/ts/plnkr.html) and download the source code from there.
|
||||
|
|
Loading…
Reference in New Issue