2015-10-17 13:01:41 -04:00
|
|
|
include ../../../../_includes/_util-fns
|
|
|
|
|
2015-11-10 13:31:46 -05:00
|
|
|
:marked
|
2015-12-10 12:40:54 -05:00
|
|
|
In most applications, users navigate from one [view](./glossary.html#view) to the next
|
|
|
|
as they perform application tasks.
|
|
|
|
|
|
|
|
The browser is a familiar model of application navigation.
|
|
|
|
We enter a URL in the address bar and the browser navigates to a corresponding page.
|
|
|
|
We click links on the page and the browser navigates to a new page.
|
|
|
|
We click the browser's back and forward buttons and the browser navigates
|
|
|
|
backward and forward through the history of pages we've seen.
|
|
|
|
|
|
|
|
The Angular "**Component Router**" (AKA "the router") borrows from this model.
|
|
|
|
It can interpret a browser URL as an instruction
|
|
|
|
to navigate to a client-generated view and pass optional parameters along to the supporting view component
|
|
|
|
to help it decide what specific content to present.
|
|
|
|
We can bind the router to links on a page and it will navigate to
|
|
|
|
the appropriate application view when the user clicks a link.
|
|
|
|
We can navigate imperatively when the user clicks a button, selects from a drop box,
|
|
|
|
or in response to some other stimulus from any source. And the router logs activity
|
|
|
|
in the browser's history journal so the back and forward buttons work as well.
|
|
|
|
|
|
|
|
[Live Example](/resources/live-examples/router/ts/src/plnkr.html).
|
|
|
|
.l-main-section
|
|
|
|
:marked
|
|
|
|
## The Basics
|
|
|
|
Let's begin with a few core concepts of the Component Router.
|
|
|
|
Then we can explore the details through a sequence of examples.
|
|
|
|
|
|
|
|
The **`Router`** is a service that presents a particular Component view for a given URL.
|
|
|
|
When the browser's URL changes, the router looks for a corresponding **`RouteDefinition`**
|
|
|
|
from which it can determine the Component to display.
|
|
|
|
|
|
|
|
A new router has no route definitions. We have to configure it.
|
|
|
|
The preferred way to configure the router is with a **`@RouteConfig`** [decorator](glossary.html#decorator)
|
|
|
|
applied to a host component.
|
|
|
|
|
|
|
|
In this example, we configure the top-level `AppComponent` with three route definitions
|
|
|
|
+makeExample('router/ts/app/app.component.2.ts', 'route-config', 'app.component.ts (excerpt)')(format=".")
|
|
|
|
:marked
|
|
|
|
|
|
|
|
.l-sub-section
|
|
|
|
:marked
|
|
|
|
There are several flavors of `RouteDefinition`.
|
|
|
|
The most common by far is the named **`Route`** which maps a URL path to a Component
|
|
|
|
|
|
|
|
The `name` field is the name of the `Route`. The name **must** be spelled in **PascalCase**.
|
|
|
|
|
|
|
|
The `:id` in the third route is a token for a route parameter. In a URL such as `/hero/42`, "42"
|
|
|
|
is the value of the `id` parameter. The corresponding `HeroDetailComponent`
|
|
|
|
will use that value to find and present the hero whose `id` is 42.
|
|
|
|
We'll learn more about route parameters later in this chapter.
|
|
|
|
: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.
|
|
|
|
code-example(format="", language="html").
|
|
|
|
<!-- Routed views go here -->
|
|
|
|
<router-outlet></router-outlet>
|
|
|
|
:marked
|
|
|
|
Now we have routes configured and a place to render them, but
|
|
|
|
how do we navigate? The URL could arrive directly from the browser address bar.
|
|
|
|
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
|
|
|
|
into a URL and a component view.
|
|
|
|
|
|
|
|
We see such bindings in the following `AppComponent` template:
|
|
|
|
+makeExample('router/ts/app/app.component.1.ts', 'template')(format=".")
|
|
|
|
.l-sub-section
|
|
|
|
:marked
|
|
|
|
We're adding two anchor tags with `RouterLink` directives.
|
|
|
|
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.
|
|
|
|
:marked
|
|
|
|
### Let's summarize
|
|
|
|
|
|
|
|
The `@RouterConfig` configuration tied the `AppComponent` to a router configured with routes.
|
|
|
|
The component has a `RouterOutlet` where it can display views produced by the router.
|
|
|
|
It has `RouterLinks` that users can click to navigate via the router.
|
|
|
|
|
|
|
|
The `AppComponent` has become a ***Routing Component***, a component that can route.
|
|
|
|
|
|
|
|
Here are the key *Component Router* terms and their meanings:
|
|
|
|
table
|
|
|
|
tr
|
|
|
|
th Router Part
|
|
|
|
th Meaning
|
|
|
|
tr
|
|
|
|
td <code>Router</code>
|
|
|
|
td.
|
|
|
|
Displays the application component for the active URL.
|
|
|
|
Manages navigation from one component to the next.
|
|
|
|
tr
|
|
|
|
td <code>@RouteConfig</code>
|
|
|
|
td.
|
|
|
|
Configures a router with <code>RouteDefinitions</code>, each mapping a URL path to a Component.
|
|
|
|
tr
|
|
|
|
td <code>RouteDefinition</code>
|
|
|
|
td.
|
|
|
|
Defines how the router should navigate to a Component based on a URL pattern.
|
|
|
|
tr
|
|
|
|
td <code>Route</code>
|
|
|
|
td.
|
|
|
|
The most common form of <code>RouteDefinition</code> consisting of a path, a route name,
|
|
|
|
and a component type.
|
|
|
|
tr
|
|
|
|
td <code>RouterOutlet</code>
|
|
|
|
td.
|
|
|
|
The directive (<code><router-outlet></code>) that marks where the router should display a view.
|
|
|
|
tr
|
|
|
|
td <code>RouterLink</code>
|
|
|
|
td.
|
|
|
|
The directive for binding a clickable HTML element to
|
|
|
|
a route. Clicking an anchor tag with a <code>routerLink</code> directive
|
|
|
|
that is bound to a <i>Link Parameters Array</i> triggers a navigation.
|
|
|
|
tr
|
|
|
|
td <i>Link Parameters Array</i></code>
|
|
|
|
td.
|
|
|
|
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.
|
|
|
|
:marked
|
|
|
|
We'll learn many more details in this chapter which covers
|
|
|
|
|
|
|
|
* configuring a router
|
|
|
|
* the link parameter arrays that propel router navigation
|
|
|
|
* navigating when the user clicks a data-bound link
|
|
|
|
* navigating under program control
|
|
|
|
* passing information in route parameters
|
|
|
|
* creating a child router with its own routes
|
|
|
|
* setting a default route (the *otherwise* option)
|
|
|
|
* asking the user's permission to leave before navigating to a new view using lifecycle events
|
|
|
|
|
|
|
|
We will proceed in phases marked by milestones.
|
|
|
|
Our first milestone is the ability to navigate between 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 introduced in the [QuickStart](../quickstart.html) and
|
|
|
|
the [Tour of Heroes](../tutorial/) tutorial.
|
|
|
|
|
|
|
|
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/src/plnkr.html).
|
|
|
|
|
|
|
|
.callout.is-critical
|
|
|
|
header Route Link Syntax - Note to self
|
|
|
|
:marked
|
|
|
|
The tutorial approach won't be the best way to fully describe the link parameters array.
|
|
|
|
|
|
|
|
Create an appendix on route link syntax ... how the array is interpreted ... and
|
|
|
|
link to it at the appropriate time.
|
|
|
|
|
|
|
|
.l-main-section
|
|
|
|
:marked
|
|
|
|
## The Sample Application
|
|
|
|
We have an application in mind as we move from milestone to milestone.
|
|
|
|
Our client is the Hero Employment Agency.
|
|
|
|
Heroes need work and The Agency finds Crises for them to solve.
|
|
|
|
|
|
|
|
The application has two main feature areas:
|
|
|
|
1. A *Crisis Center* where we maintain the list of crises for assignment to heroes.
|
|
|
|
1. A *Heroes* area where we maintain the list of heroes employed by The Agency.
|
|
|
|
|
|
|
|
Run the [live example](/resources/live-examples/router/ts/src/plnkr.html).
|
|
|
|
It opens in the *Crisis Center*. We'll come back to that.
|
|
|
|
|
|
|
|
Click the *Heroes* link. We're presented with a list of Heroes.
|
|
|
|
figure.image-display
|
|
|
|
img(src='/resources/images/devguide/router/hero-list.png' alt="Hero List" width="250")
|
|
|
|
:marked
|
|
|
|
We select one and the applications takes us to a hero editing screen.
|
|
|
|
figure.image-display
|
|
|
|
img(src='/resources/images/devguide/router/hero-detail.png' alt="Crisis Center Detail" width="250")
|
|
|
|
:marked
|
|
|
|
Our changes take affect immediately. We click the "Back" button and the
|
|
|
|
app returns us to the Heroes list.
|
|
|
|
|
|
|
|
We could have clicked the browser's back button instead.
|
|
|
|
That would have returned us to the Heroes List as well.
|
|
|
|
Angular app navigation updates the browser history as normal web navigation does.
|
|
|
|
|
|
|
|
Now click the *Crisis Center* link. We go to the *Crisis Center* and its list of ongoing crises.
|
|
|
|
figure.image-display
|
|
|
|
img(src='/resources/images/devguide/router/crisis-center-list.png' alt="Crisis Center List" )
|
|
|
|
:marked
|
|
|
|
We select one and the applications takes us to a crisis editing screen.
|
|
|
|
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.
|
|
|
|
|
|
|
|
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.
|
|
|
|
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
|
|
|
|
or ask the user's permission before navigating away from the current view.
|
|
|
|
|
|
|
|
Let's see a quick demonstration of the workflow in action.
|
|
|
|
<a id="full-app-demo"></a>
|
|
|
|
figure.image-display
|
|
|
|
img(src='/resources/images/devguide/router/router-anim.gif' alt="App in action" )
|
|
|
|
:marked
|
|
|
|
Here's a diagram of all application routing options:
|
|
|
|
figure.image-display
|
|
|
|
img(src='/resources/images/devguide/router/complete-nav.png' alt="Navigation diagram" )
|
|
|
|
:marked
|
|
|
|
This app illustrates the router features we'll cover in this chapter
|
|
|
|
|
|
|
|
* 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 `CanLeave` lifecycle method (ask before discarding changes)
|
2015-11-10 13:31:46 -05:00
|
|
|
|
2015-12-10 12:40:54 -05:00
|
|
|
<a id="getting-started"></a>
|
2015-10-17 13:01:41 -04:00
|
|
|
.l-main-section
|
2015-11-10 13:31:46 -05:00
|
|
|
:marked
|
2015-12-10 12:40:54 -05:00
|
|
|
## Milestone #1: Getting Started with the Router
|
|
|
|
|
|
|
|
Let's begin with a simple version of the app that navigates between two empty views.
|
|
|
|
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 router is an optional service and you might prefer a different router someday.
|
|
|
|
|
|
|
|
The Component Router library is part of the Angular npm bundle.
|
|
|
|
We make it available by loading its script in our `index.html`, right after
|
|
|
|
the Angular core script.
|
|
|
|
+makeExample('router/ts/index.html','router-lib')(format=".")
|
|
|
|
:marked
|
|
|
|
### Set the *<base href>*
|
|
|
|
The Component Router uses the browser's
|
|
|
|
[history.pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries)
|
|
|
|
for navigation. Thanks to `pushState`, we can make our in-app URL paths look the way we want them to
|
|
|
|
look, e.g. `localhost:3000/crisis-center`. Our in-app URLs can be indistinguishable from server URLs.
|
|
|
|
|
|
|
|
Modern HTML 5 browsers were the first to support `pushState` which is why many people refer to these URLs as
|
|
|
|
"HTML 5 style" URLs.
|
|
|
|
|
|
|
|
We must **add a [<base href> element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag**
|
|
|
|
to the `index.html` to make this work.
|
|
|
|
The `href` value specifies the base URL to use for all *relative* URLs within a document including
|
|
|
|
links to css files, scripts, and images.
|
|
|
|
|
|
|
|
Add the base element just after the `<head>` tag.
|
|
|
|
If the `app` folder is the application root, as it is for our application,
|
|
|
|
set the `href` value *exactly* as shown here.
|
|
|
|
|
|
|
|
+makeExample('router/ts/index.html','base-href')(format=".")
|
|
|
|
.l-sub-section
|
|
|
|
:marked
|
|
|
|
HTML 5 style navigation is the Component Router default.
|
|
|
|
Learn why "HTML 5" style is preferred, how to adjust its behavior, and how to switch to the
|
|
|
|
older hash (#) style if necessary in the [Browser URL Styles](#browser-url-styles) appendix below.
|
|
|
|
|
|
|
|
:marked
|
|
|
|
.l-sub-section
|
|
|
|
:marked
|
|
|
|
#### Live example note
|
|
|
|
We have to be get tricky when we run the live example because the host service sets
|
|
|
|
the application base address dynamically. That's why we replace the `<base href...>` with a
|
|
|
|
script that writes a `<base>` tag on the fly to match.
|
|
|
|
code-example(format="")
|
|
|
|
<script>document.write('<base href="' + document.location + '" />');</script>
|
|
|
|
:marked
|
|
|
|
We should only need this trick for the live example, not production code.
|
|
|
|
:marked
|
|
|
|
### Booting with the router service providers
|
|
|
|
Our app launches from the `boot.ts` file in the `~/app` folder so let's start there.
|
|
|
|
It's short and all of it is relevant to routing.
|
|
|
|
+makeExample('router/ts/app/boot.1.ts','all', 'boot.ts')(format=".")
|
|
|
|
: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
|
|
|
|
`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.
|
|
|
|
.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:
|
|
|
|
figure.image-display
|
|
|
|
img(src='/resources/images/devguide/router/shell-and-outlet.png' alt="Shell" width="300" )
|
|
|
|
:marked
|
|
|
|
<a id="shell-template"></a>
|
|
|
|
The corresponding component template looks like this:
|
|
|
|
+makeExample('router/ts/app/app.component.1.ts','template')(format=".")
|
|
|
|
:marked
|
|
|
|
### *RouterOutlet*
|
|
|
|
`RouterOutlet` is a component from the router library.
|
|
|
|
The router displays views within the bounds of the `<router-outlet>` tags.
|
|
|
|
|
|
|
|
.l-sub-section
|
|
|
|
:marked
|
|
|
|
A template may hold exactly one ***unnamed*** `<router-outlet>`.
|
|
|
|
|
|
|
|
It could have multiple ***named*** outlets (e.g., `<router-outlet name="chat">`).
|
|
|
|
We'll learn about them when we get to "auxiliary routes".
|
|
|
|
: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 template expression to the right of the equals (=) returns an *array of link parameters*.
|
|
|
|
|
|
|
|
The arrays in this example each have a single string parameter, the name of a `Route` that
|
|
|
|
we'll configure for this application with `@RouteConfig()`.
|
|
|
|
|
|
|
|
### *@RouteConfig()*
|
|
|
|
A router holds a list of route definitions. The list is empty for a new router. We must configure it.
|
|
|
|
|
|
|
|
A router also needs a **Host Component**, a point of origin for its navigations.
|
|
|
|
|
|
|
|
It's natural to combine the creation of a new router, its configuration, and its assignment to a host component
|
|
|
|
in a single step. That's the purpose of the `@RouteConfig` decorator which we put to good use here:
|
|
|
|
+makeExample('router/ts/app/app.component.1.ts','route-config')(format=".")
|
|
|
|
:marked
|
|
|
|
The `@RouteConfig` decorator creates a new router.
|
|
|
|
We applied the decorator to `AppComponent` which makes that the router's host component.
|
|
|
|
The argument to `@RouteConfig()` is an array of **Route Definitions**.
|
|
|
|
|
|
|
|
We're supplying two definitions:
|
|
|
|
+makeExample('router/ts/app/app.component.1.ts','route-defs')(format=".")
|
|
|
|
:marked
|
|
|
|
Each definition translates to a [Route](https://angular.io/docs/ts/latest/api/router/Route-class.html) which has a
|
|
|
|
* `path` - the URL path segment for this route
|
|
|
|
* `name` - the name of the route
|
|
|
|
* `component` - the Component associated with this route.
|
|
|
|
|
|
|
|
The router draws upon its registry of route definition when
|
|
|
|
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:
|
|
|
|
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.*
|
|
|
|
|
|
|
|
1. *When the application requests navigation to a route **named** `CrisisCenter`, compose a browser URL
|
|
|
|
with the path segment `/crisis-center`, update the browser's address location and history, create or retrieve an instance of
|
|
|
|
the `CrisisCenterComponent`, and display that component's view.*
|
|
|
|
|
|
|
|
### "Getting Started" wrap-up
|
|
|
|
|
|
|
|
We've got a very basic, navigating app, one that can switch between two views
|
|
|
|
when the user clicks a link.
|
|
|
|
|
|
|
|
We've learned how to
|
|
|
|
* load the router library
|
|
|
|
* add a nav bar to the shell template with anchor tags and `routerLink` directives
|
|
|
|
* added a `router-outlet` to the shell template where views will be displayed
|
|
|
|
* configure the router with `@RouterConfig`
|
|
|
|
* 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.
|
|
|
|
|
|
|
|
Our starter app's structure looks like this:
|
|
|
|
code-example(format="").
|
|
|
|
router-sample
|
|
|
|
├── node_modules/
|
|
|
|
├── app/
|
|
|
|
| ├── app.component.ts
|
|
|
|
│ ├── boot.ts
|
|
|
|
│ ├── crisis-list.component.ts
|
|
|
|
│ └── hero-list.component.ts
|
|
|
|
├── index.html
|
|
|
|
├── styles.css
|
|
|
|
├── tsconfig.json
|
|
|
|
└── package.json
|
|
|
|
:marked
|
|
|
|
Here are the application-specific files
|
|
|
|
+makeTabs(
|
|
|
|
`router/ts/app/app.component.1.ts,
|
|
|
|
router/ts/app/boot.1.ts,
|
|
|
|
router/ts/app/hero-list.component.ts,
|
|
|
|
router/ts/app/crisis-list.component.ts`,
|
|
|
|
',all,,',
|
|
|
|
`app.component.ts, boot.ts,hero-list.component.ts,crisis-list.component.ts`)
|
|
|
|
:marked
|
|
|
|
|
|
|
|
<a id="heroes-feature"></a>
|
|
|
|
.l-main-section
|
|
|
|
:marked
|
|
|
|
## Milestone #2: The Heroes Feature
|
|
|
|
|
|
|
|
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"
|
|
|
|
* 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.
|
|
|
|
|
|
|
|
### 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).
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
Our first step is to **create a separate `app/heroes/` folder**.
|
|
|
|
Then we'll add Hero management feature files.
|
|
|
|
|
|
|
|
We won't be creative about this. 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
|
|
|
|
figure.image-display
|
|
|
|
img(src='/resources/images/devguide/router/router-2-anim.gif' alt="App in action" )
|
|
|
|
:marked
|
|
|
|
### Add Heroes functionality
|
|
|
|
|
|
|
|
We delete the placeholder `hero-list.component.ts` that's in
|
|
|
|
the `app/` folder.
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
When were done organizing, we have three "Hero" files:
|
|
|
|
|
|
|
|
code-example(format="").
|
|
|
|
app/heroes/
|
|
|
|
├── hero-detail.component.ts
|
|
|
|
├── hero-list.component.ts
|
|
|
|
└── 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`) .
|
|
|
|
|
|
|
|
Now it's time for some surgery to bring these files and the rest of the app
|
|
|
|
into alignment with our application router.
|
|
|
|
|
|
|
|
### New route definition with route parameter
|
|
|
|
|
|
|
|
The new Heroes feature has two interacting components, the list and the detail.
|
|
|
|
The list view is self-sufficient; we navigate to it, it gets a list of heroes and displays them.
|
|
|
|
It doesn't need any outside information.
|
|
|
|
|
|
|
|
The detail view is different. It displays a particular hero. It can't know which hero on its own.
|
|
|
|
That information must come from outside.
|
|
|
|
|
|
|
|
In our example, when the user selects a hero from the list, we navigate to the detail view to show that hero.
|
|
|
|
We'll tell the detail view which hero to display by including the selected hero's id in the route URL.
|
|
|
|
|
|
|
|
With that plan in mind, we return to the `app.component.ts` to make changes to the router's configuration
|
|
|
|
|
|
|
|
First, we import the two components from their new locations in the `app/heroes/` folder:
|
|
|
|
+makeExample('router/ts/app/app.component.2.ts','hero-import')(format=".")
|
|
|
|
:marked
|
|
|
|
Then we update the `@RouteConfig` route definitions :
|
|
|
|
+makeExample('router/ts/app/app.component.2.ts','route-config')(format=".")
|
|
|
|
:marked
|
|
|
|
The `CrisisCenter` and `Heroes` definitions didn't change.
|
|
|
|
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.
|
|
|
|
+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
|
|
|
|
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
|
|
|
|
pattern and go to the same "Magenta" detail view.
|
|
|
|
|
|
|
|
### Navigate to the detail imperatively
|
|
|
|
|
|
|
|
*We don't navigate to the detail component by clicking a link*.
|
|
|
|
We won't be adding a new anchor tag to the shell navigation bar.
|
|
|
|
|
|
|
|
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):
|
|
|
|
+makeExample('router/ts/app/heroes/hero-list.component.ts','ctor')(format=".")
|
|
|
|
:marked
|
|
|
|
Here's the `select` method:
|
|
|
|
+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.
|
|
|
|
### Setting the route parameter
|
|
|
|
|
|
|
|
We're navigating to the `HeroDetailComponent` where we expect to see the details of the selected hero.
|
|
|
|
We'll need *two* pieces of information: the destination and the hero's `id`.
|
|
|
|
|
|
|
|
Accordingly, the *link parameters array* has *two* items: the **name** of the destination route and a **route parameters object** that specifies the
|
|
|
|
`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:
|
|
|
|
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`?
|
|
|
|
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
|
|
|
|
the `HeroDetailComponent` via the **RouteParams** service.
|
|
|
|
|
|
|
|
As usual, we write a constructor that asks Angular to inject that service among the other services
|
|
|
|
that the component require and reference them as private variables.
|
|
|
|
+makeExample('router/ts/app/heroes/hero-detail.component.ts','ctor')(format=".")
|
|
|
|
:marked
|
|
|
|
Later, in the `ngOnInit` method,
|
|
|
|
we ask the `RouteParams` service for the `id` parameter by name and
|
|
|
|
tell the `HeroService` to fetch the hero with that `id`.
|
|
|
|
+makeExample('router/ts/app/heroes/hero-detail.component.ts','ngOnInit')(format=".")
|
|
|
|
|
|
|
|
.l-sub-section
|
|
|
|
:marked
|
|
|
|
Angular calls the `ngOnInit` method shortly after creating an instance of the `HeroDetailComponent`.
|
|
|
|
|
|
|
|
We put the data access logic in the `ngOnInit` method rather than inside the constructor
|
|
|
|
to improve the component's testability.
|
|
|
|
We explore this point in greater detail in the [OnInit appendix](#onInit) below.
|
|
|
|
:marked
|
|
|
|
### Navigating back to the list component
|
|
|
|
The `HeroDetailComponent` has a "Back" button wired to its `gotoHeroes` method that navigates imperatively
|
|
|
|
back to the `HeroListComponent`.
|
|
|
|
|
|
|
|
The router `navigate` method takes the same, one-item *link parameters array*, holding
|
|
|
|
the **name of the `HeroListComponent` route**, that we used in the `[routerLink]` directive binding.
|
|
|
|
+makeExample('router/ts/app/heroes/hero-detail.component.ts','gotoHeroes')(format=".")
|
|
|
|
:marked
|
|
|
|
### Heroes App Wrap-up
|
|
|
|
|
|
|
|
We've reached the second milestone in our router education.
|
|
|
|
|
|
|
|
We've learned how to
|
|
|
|
* organize our app into "feature areas"
|
|
|
|
* navigate imperatively from one component to another
|
|
|
|
* pass information along in route parameters (`RouteParams`)
|
|
|
|
|
|
|
|
After these changes, the folder structure looks like this:
|
|
|
|
code-example(format="").
|
|
|
|
router-sample
|
|
|
|
├── node_modules/
|
|
|
|
├── app/
|
|
|
|
│ ├── heroes/
|
|
|
|
│ │ ├── hero-detail.component.ts
|
|
|
|
│ │ ├── hero-list.component.ts
|
|
|
|
│ │ └── hero.service.ts
|
|
|
|
│ ├── app.component.ts
|
|
|
|
| ├── boot.ts
|
|
|
|
│ └── crisis-list.component.ts
|
|
|
|
├── index.html
|
|
|
|
├── styles.css
|
|
|
|
├── tsconfig.json
|
|
|
|
└── package.json
|
|
|
|
:marked
|
|
|
|
<a id="heroes-app-code"></a>
|
|
|
|
### The Heroes App code
|
|
|
|
Here are the relevant files for this version of the sample application.
|
|
|
|
+makeTabs(
|
|
|
|
`router/ts/app/app.component.2.ts,
|
|
|
|
router/ts/app/boot.2.ts,
|
|
|
|
router/ts/app/heroes/hero-list.component.ts,
|
|
|
|
router/ts/app/heroes/hero-detail.component.ts,
|
|
|
|
router/ts/app/heroes/hero.service.ts`,
|
|
|
|
`,v2,,,`,
|
|
|
|
`app.component.ts,
|
|
|
|
boot.ts,
|
|
|
|
hero-list.component.ts,
|
|
|
|
hero-detail.component.ts,
|
|
|
|
hero.service.ts`)
|
|
|
|
:marked
|
|
|
|
|
|
|
|
<a id="crisis-center-feature"></a>
|
|
|
|
.l-main-section
|
|
|
|
:marked
|
|
|
|
## Milestone #3: The Crisis Center
|
|
|
|
The *Crisis Center* is a fake view at the moment. Time to make it useful.
|
|
|
|
|
|
|
|
The new *Crisis Center* begins as a virtual copy of the *Heroes* feature.
|
|
|
|
We create a new `app/crisis-center` folder, copy the Hero files,
|
|
|
|
and change every mention of "hero" to "crisis".
|
|
|
|
|
|
|
|
A `Crisis` has an `id` and `name`, just like a `Hero`
|
|
|
|
The new `CrisisListComponent` displays lists of crises.
|
|
|
|
When the user selects a crisis, the app navigates to the `CrisisDetailComponent`
|
|
|
|
for display and editing of the crisis name.
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
* The application should navigate to the *Crisis Center* by default.
|
|
|
|
|
|
|
|
* The user should be able to cancel unwanted changes.
|
|
|
|
|
|
|
|
* The router should prevent navigation away from the detail view while there are pending changes.
|
|
|
|
|
|
|
|
* When we return to the list from the detail, the previously edited crisis should be pre-selected in the list.
|
|
|
|
That will require passing information *back* to the list from the detail.
|
|
|
|
|
|
|
|
There are also a few lingering annoyances in the *Heroes* implementation that we can cure in the *Crisis Center*.
|
|
|
|
|
|
|
|
* We currently register every route of every view at the highest level of the application.
|
|
|
|
If we expand the *Crisis Center* with a 100 new views, we'll make 100 changes to the
|
|
|
|
`AppComponent` route configuration. If we rename a *Crisis Center* component or change a route definition,
|
|
|
|
we'll be changing the `AppComponent` too.
|
|
|
|
|
|
|
|
* If we followed *Heroes* lead, we'd be adding the `CrisisService` to the providers in `boot.ts`.
|
|
|
|
Now both `HeroService` and `CrisisService` would be available everywhere although
|
|
|
|
they're only needed in their respective feature modules. That stinks.
|
|
|
|
|
|
|
|
Changes to a sub-module such as *Crisis Center* shouldn't provoke changes to the `AppComponent` or `boot.ts`.
|
|
|
|
We need to [*separate our concerns*](https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html).
|
|
|
|
|
|
|
|
We'll fix all of these problems and add the new routing features to *Crisis Center*.
|
|
|
|
|
|
|
|
The most important fix from a router perspective will be the introduction of a **child *Routing Component***
|
|
|
|
with 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*.
|
|
|
|
|
|
|
|
### A free-standing Crisis Center Feature Module
|
|
|
|
The *Crisis Center* is one of two application workflows.
|
|
|
|
Users navigate between them depending on whether they are managing crises or heroes.
|
|
|
|
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
But we're right to be nervous about the way *Heroes* and *Crisis Center* artifacts are
|
|
|
|
bubbling up to the root `AppComponent` and blending with each other.
|
|
|
|
That's a [code smell](http://martinfowler.com/bliki/CodeSmell.html).
|
|
|
|
|
|
|
|
Isolating feature area modules from each other looks good to us.
|
|
|
|
.l-sub-section
|
|
|
|
:marked
|
|
|
|
It's looking good as a general pattern for Angular applications.
|
|
|
|
figure.image-display
|
|
|
|
img(src='/resources/images/devguide/router/component-tree.png' alt="Component Tree" )
|
|
|
|
:marked
|
|
|
|
* each feature area in its own module folder
|
|
|
|
* each area with its own root component
|
|
|
|
* each area root component with its own router-outlet and child routes
|
|
|
|
* area routes rarely (if ever) cross
|
|
|
|
|
|
|
|
:marked
|
|
|
|
We'll make the *Crisis Center* stand on its own and leave the *Heroes* as it is
|
|
|
|
so we can compare the effort, results, and consequences.
|
|
|
|
Then each of us can decide which path to prefer (as if we didn't already know).
|
|
|
|
|
|
|
|
### Child Routing Component
|
|
|
|
We create a new `app/crisis-center` folder and add `crisis-center-component.ts` to it with the following contents:
|
|
|
|
+makeExample('router/ts/app/crisis-center/crisis-center.component.ts', 'minus-imports', 'crisis-center/crisis-center.component.ts (minus imports)')
|
|
|
|
:marked
|
|
|
|
The `CrisisCenterComponent` parallels the `AppComponent`.
|
|
|
|
|
|
|
|
The `CrisisCenterComponent` is the root of the *Crisis Center* area
|
|
|
|
just as `AppComponent` is the root of the entire application.
|
|
|
|
|
|
|
|
This `CrisisCenterComponent` is a shell for crisis management
|
|
|
|
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` 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.
|
|
|
|
|
|
|
|
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
|
|
|
|
We add the `CrisisService` to the component's providers array
|
|
|
|
instead of registering it with the `bootstrap` function in `boot.ts`.
|
|
|
|
+makeExample('router/ts/app/crisis-center/crisis-center.component.ts', 'providers')
|
|
|
|
:marked
|
|
|
|
This step limits the scope of that service to the *Crisis Center* component and its sub-component tree.
|
|
|
|
No component outside of the *Crisis Center* needs access to the `CrisisService`.
|
|
|
|
By restricting its scope, we feel confident that we can evolve it independently without fear of breaking
|
|
|
|
unrelated application modules — modules that *shouldn't have access to it anyway*.
|
|
|
|
|
|
|
|
### Child Route Configuration
|
|
|
|
The `CrisisCenterComponent` is a *Routing Component* like the `AppComponent`.
|
|
|
|
|
|
|
|
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', 'crisis-center/crisis-center.component.ts (routes only)' )
|
|
|
|
: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
|
|
|
|
can guess by their names.
|
|
|
|
|
|
|
|
We cannot tell just 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.
|
|
|
|
|
|
|
|
*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.
|
|
|
|
:marked
|
|
|
|
### Parent Route Configuration
|
|
|
|
Here is is the revised route configuration for the parent `AppComponent`:
|
|
|
|
+makeExample('router/ts/app/app.component.ts', 'route-config', 'app/app.component.ts (routes only)' )
|
|
|
|
:marked
|
|
|
|
The second and third *Hero* routes haven't changed.
|
|
|
|
The first *Crisis Center* route has changed — *signficantly* — and we've formatted it to draw attention to the differences:
|
|
|
|
+makeExample('router/ts/app/app.component.ts', 'route-config-cc')(format=".")
|
|
|
|
:marked
|
|
|
|
Notice that the **path ends with a slash and three trailing periods (`/...`)**.
|
|
|
|
|
|
|
|
That means this is a ***non-terminal route*** , a route that requires completion by a **child router**
|
|
|
|
attached to the designated component which must be a *Routing Component*.
|
|
|
|
|
|
|
|
All is well.
|
|
|
|
The route's component is the `CrisisCenterComponent` which we know to be a *Routing Component* with its own routes.
|
|
|
|
|
|
|
|
<a id="default"></a>
|
|
|
|
<a id="otherwise"></a>
|
|
|
|
### Default route (AKA *otherwise*)
|
|
|
|
The other big change is the addition of the `useAsDefault` property.
|
|
|
|
Its value is `true` which makes *this* route the *default* route.
|
|
|
|
|
|
|
|
When the `AppComponent` router sees a URL that doesn't match any of these three route paths,
|
|
|
|
it redirects to this 'CrisisCenter' route.
|
|
|
|
.l-sub-section
|
|
|
|
:marked
|
|
|
|
Setting `useAsDefault = true` is the equivalent of an ***otherwise*** in other routing systems.
|
|
|
|
:marked
|
|
|
|
That's how we get to the *Crisis Center* when we first launch the application.
|
|
|
|
At launch the URL is a host and port with no path. That doesn't match any the configured route paths.
|
|
|
|
So the router redirects to the *Crisis Center*.
|
|
|
|
|
|
|
|
Try any bogus address in the [live example](/resources/live-examples/router/ts/src/plnkr.html) and
|
|
|
|
we'll land back in the *Crisis Center*.
|
|
|
|
[NOT TRUE. SHOULD BE TRUE?]
|
|
|
|
:marked
|
|
|
|
|
|
|
|
:marked
|
|
|
|
### PICK UP HERE
|
|
|
|
TODO:
|
|
|
|
* The RouteLink to the Crisis Center ... and how it doesn't specify the child route ... but could
|
|
|
|
* The route name prefixes in the route links (push that below)
|
|
|
|
* query parameters (no time to build an example I'm afraid)
|
|
|
|
* The router lifecycle hooks below
|
|
|
|
* decide whether to keep the # option discussion
|
|
|
|
* How the router interprets the link parameters array
|
|
|
|
|
|
|
|
### Cancel and Save
|
|
|
|
[INTRO]
|
|
|
|
code-example(format=".").
|
|
|
|
<button (click)="save()">Save</button>
|
|
|
|
<button (click)="cancel()">Cancel</button>
|
|
|
|
:marked
|
|
|
|
[EXPLAIN]
|
|
|
|
+makeExample('router/ts/app/crisis-center/crisis-detail.component.ts', 'cancel-save', 'crisis-detail.component.ts (excerpt)')
|
|
|
|
:marked
|
|
|
|
[EXPLANATION]
|
|
|
|
<a id="canDeactivate"></a>
|
|
|
|
### Confirm before leaving with unsaved changes
|
|
|
|
[INTRO]
|
|
|
|
+makeExample('router/ts/app/crisis-center/crisis-detail.component.ts', 'canDeactivate', 'crisis-detail.component.ts (excerpt)')
|
|
|
|
:marked
|
|
|
|
[EXPLANATION]
|
|
|
|
### Re-select crisis in the list via route param
|
|
|
|
[INTRO]
|
|
|
|
+makeExample('router/ts/app/crisis-center/crisis-detail.component.ts', 'gotoCrises', 'crisis-detail.component.ts (excerpt)')
|
|
|
|
:marked
|
|
|
|
[EXPLANATION]
|
|
|
|
+makeExample('router/ts/app/crisis-center/crisis-list.component.ts', 'isSelected', 'crisis-list.component.ts (excerpt)')
|
|
|
|
:marked
|
|
|
|
[EXPLANATION]
|
|
|
|
code-example(format=".").
|
|
|
|
[class.selected]="isSelected(crisis)"
|
|
|
|
:marked
|
|
|
|
|
|
|
|
|
|
|
|
<a id="final-app"></a>
|
|
|
|
.l-main-section
|
|
|
|
:marked
|
|
|
|
## The Finished App
|
|
|
|
As we end our chapter together, we take a parting look at
|
|
|
|
the entire application.
|
|
|
|
|
|
|
|
### Folder structure
|
|
|
|
Our final project folder structure looks like this:
|
|
|
|
code-example(format="").
|
|
|
|
router-sample
|
|
|
|
├── node_modules/
|
|
|
|
├── src/
|
|
|
|
├── app/
|
|
|
|
│ ├── crisis-center/...
|
|
|
|
│ ├── heroes/...
|
|
|
|
│ ├── app.component.ts
|
|
|
|
│ ├── boot.ts
|
|
|
|
│ └── dialog.service.ts
|
|
|
|
├── index.html
|
|
|
|
├── styles.css
|
|
|
|
├── tsconfig.json
|
|
|
|
└── package.json
|
|
|
|
:marked
|
|
|
|
The top level application files are
|
|
|
|
+makeTabs(
|
|
|
|
`router/ts/app/app.component.ts,
|
|
|
|
router/ts/app/boot.ts,
|
|
|
|
router/ts/app/dialog.service.ts,
|
|
|
|
router/ts/index.html,
|
|
|
|
router/ts/styles.css
|
|
|
|
`,
|
|
|
|
null,
|
|
|
|
`app.component.ts,
|
|
|
|
boot.ts,
|
|
|
|
dialog.service.ts,
|
|
|
|
index.html,
|
|
|
|
styles.css
|
|
|
|
`)
|
|
|
|
:marked
|
|
|
|
<a id="crisis-center-structure-and-code"></id>
|
|
|
|
### Crisis Center
|
|
|
|
The *Crisis Center* feature area within the `crisis-center` folder follows:
|
|
|
|
code-example(format="").
|
|
|
|
app/
|
|
|
|
crisis-center/
|
|
|
|
├── crisis-center.component.ts
|
|
|
|
├── crisis-detail.component.ts
|
|
|
|
├── crisis-list.component.ts
|
|
|
|
├── crisis.service.ts
|
|
|
|
└── routes.ts
|
|
|
|
:marked
|
|
|
|
+makeTabs(
|
|
|
|
`router/ts/app/crisis-center/crisis-center.component.ts,
|
|
|
|
router/ts/app/crisis-center/crisis-list.component.ts,
|
|
|
|
router/ts/app/crisis-center/crisis-detail.component.ts,
|
|
|
|
router/ts/app/crisis-center/crisis.service.ts
|
|
|
|
`,
|
|
|
|
null,
|
|
|
|
`crisis-center.component.ts,
|
|
|
|
crisis-list.component.ts,
|
|
|
|
crisis-detail.component.ts,
|
|
|
|
crisis.service.ts,
|
|
|
|
`)
|
|
|
|
:marked
|
|
|
|
### Heroes
|
|
|
|
The *Heroes* feature area within the `heroes` folder is next:
|
|
|
|
code-example(format="").
|
|
|
|
app/
|
|
|
|
heroes/
|
|
|
|
├── hero-detail.component.ts
|
|
|
|
├── hero-list.component.ts
|
|
|
|
└── hero.service.ts
|
|
|
|
:marked
|
|
|
|
+makeTabs(
|
|
|
|
`router/ts/app/heroes/hero-list.component.ts,
|
|
|
|
router/ts/app/heroes/hero-detail.component.ts,
|
|
|
|
router/ts/app/heroes/hero.service.ts
|
|
|
|
`,
|
|
|
|
null,
|
|
|
|
`hero-list.component.ts,
|
|
|
|
hero-detail.component.ts,
|
|
|
|
hero.service.ts
|
|
|
|
`)
|
|
|
|
:marked
|
|
|
|
|
|
|
|
<a id="onInit"></a>
|
|
|
|
.l-main-section
|
|
|
|
:marked
|
|
|
|
## Appendix: Why use an *ngOnInit* method
|
|
|
|
|
|
|
|
We implemented an `ngOnInit` method in many of our Component classes.
|
|
|
|
We did so, for example, in the [HeroDetailComponent](#hero-detail-ctor).
|
|
|
|
We might have put the `ngOnInit` logic inside the constructor instead. We didn't for a reason. The reason is *testability*.
|
|
|
|
|
|
|
|
A constructor that has major side-effects can be difficult to test because it starts doing things as soon as
|
|
|
|
we create a test instance. In this case, it might have made a request to a remote server, something it shouldn't
|
|
|
|
do under test. It may even be impossible to reach the server in the test environment.
|
|
|
|
|
|
|
|
The better practice is to limit what the constructor can do. Mostly it should stash parameters in
|
|
|
|
local variables and perform simple instance configuration.
|
|
|
|
|
|
|
|
Yet we want an instance of this class to get the hero data from the `HeroService` soon after it is created.
|
|
|
|
How do we ensure that happens if not in the constructor?
|
|
|
|
|
|
|
|
Angular detects when a component has certain lifecycle methods like
|
|
|
|
[ngOnInit](https://angular.io/docs/ts/latest/api/core/OnInit-interface.html) and
|
|
|
|
[ngOnDestroy](https://angular.io/docs/ts/latest/api/core/OnDestroy-interface.html) and calls them
|
|
|
|
at the appropriate moment.
|
|
|
|
|
|
|
|
Angular will call `ngOnInit` when we navigate to the `HeroDetailComponent`, we'll get the `id` from the `RouteParams`
|
|
|
|
and ask the server for the hero with that `id`.
|
|
|
|
|
|
|
|
We too can call that `ngOnInit` method in our tests if we wish ... after taking control of the injected
|
|
|
|
`HeroService` and (perhaps) mocking it.
|
|
|
|
|
|
|
|
<a name="browser-url-styles"></a>
|
|
|
|
.l-main-section
|
|
|
|
:marked
|
|
|
|
## Appendix: Browser URL styles
|
|
|
|
|
|
|
|
When the router navigates to a new component view, it updates the browser's location and history
|
|
|
|
with a URL for that view.
|
|
|
|
This is a strictly local URL. The browser shouldn't send a request to the server
|
|
|
|
and should not reload the page.
|
|
|
|
.l-sub-section
|
|
|
|
:marked
|
|
|
|
We're talking now about the ***browser*** URL
|
|
|
|
**not** the *route* URL that we record in a `RouteDefinition`.
|
|
|
|
The browser URL is what we paste into the browser's **address bar**
|
|
|
|
and email to folks so they can deep-link into an application page.
|
|
|
|
:marked
|
|
|
|
Modern HTML 5 browsers support
|
|
|
|
[history.pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries),
|
|
|
|
a technique that changes a browser's location and history without triggering a server page request.
|
|
|
|
The router can compose a "natural" URL that is indistinguishable from
|
|
|
|
one that would otherwise require a page load.
|
|
|
|
|
|
|
|
Here's the *Crisis Center* URL in this "HTML 5 pushState" style:
|
|
|
|
code-example(format=".", language="bash").
|
|
|
|
localhost:3002/crisis-center/
|
|
|
|
:marked
|
|
|
|
Older browsers send page requests to the server when the location URL changes ...
|
|
|
|
unless the change occurs after a "#" (called the "hash").
|
|
|
|
Routers take advantage of this exception by composing in-application route
|
|
|
|
URLs with hashes. Here's a "hash URL" that routes to the *Crisis Center*
|
|
|
|
code-example(format=".", language="bash").
|
|
|
|
localhost:3002/src/#/crisis-center/
|
|
|
|
:marked
|
|
|
|
The Angular Component Router supports both styles.
|
|
|
|
We set our preference by providing a `LocationStrategy` during the bootstrapping process.
|
|
|
|
.l-sub-section
|
|
|
|
:marked
|
|
|
|
Learn about "providers" and the bootstrap process in the
|
|
|
|
[Dependency Injection chapter](dependency-injection#bootstrap)
|
|
|
|
:marked
|
|
|
|
### Which Strategy is Best?
|
|
|
|
We must choose a strategy and we need to make the right call early in the project.
|
|
|
|
It won't be easy to change later once the application is in production
|
|
|
|
and there are lots of application URL references in the wild.
|
|
|
|
|
|
|
|
Almost all Angular 2 projects should use the default HTML 5 style.
|
|
|
|
It produces URLs that are easier for users to understand.
|
|
|
|
And it preserves the option to do **server-side rendering** later.
|
|
|
|
|
|
|
|
Rendering critical pages on the server is a technique that can greatly improve
|
|
|
|
perceived responsiveness when the app first loads.
|
|
|
|
An app that would otherwise take ten or more seconds to start
|
|
|
|
could be rendered on the server and delivered to the user's device
|
|
|
|
in less than a second.
|
|
|
|
|
|
|
|
Thist option is only available if application URLs look like normal web URLs
|
|
|
|
without hashes (#) in the middle.
|
|
|
|
|
|
|
|
Stick with the default unless you have a compelling reason to
|
|
|
|
resort to hash routes.
|
|
|
|
|
|
|
|
### HTML 5 URLs and the *<base href>*
|
|
|
|
The router use the "[HTML 5 pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries)"
|
|
|
|
style by default.
|
|
|
|
We don't have to provide the router's `PathLocationStrategy` because it's loaded automatically.
|
|
|
|
|
|
|
|
We *must* add a
|
|
|
|
[<base href> element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag
|
|
|
|
in the `<head>` of the `index.html`.
|
|
|
|
+makeExample('router/ts/index.html','base-href')(format=".")
|
|
|
|
:marked
|
|
|
|
Without that tag, the browser may not be be able to load resources
|
|
|
|
(images, css, scripts) when "deep linking" into the app.
|
|
|
|
Bad things could happen when someone pastes an application link into the
|
|
|
|
browser's address bar or clicks such a link in an email link.
|
|
|
|
|
|
|
|
Some developers may not be able to add the `<base>` element, perhaps because they don't have
|
|
|
|
access to `<head>` or the `index.html`.
|
|
|
|
|
|
|
|
Those developers may still use HTML 5 URLs by taking two remedial steps:
|
|
|
|
|
|
|
|
1. Provide the router with an appropriate `APP_BASE_HREF` value.
|
|
|
|
1. Use **absolute URLs** for all web resources: css, images, scripts, and template html files.
|
|
|
|
|
|
|
|
.l-sub-section
|
|
|
|
:marked
|
|
|
|
Learn about the [APP_BASE_HREF](https://angular.io/docs/ts/latest/api/router/APP_BASE_HREF-const.html)
|
|
|
|
in the API Guide.
|
|
|
|
:marked
|
|
|
|
### *HashLocationStrategy*
|
|
|
|
We can go old-school with the `HashLocationStrategy` by
|
|
|
|
providing it as the router's `LocationStrategy` during application bootstrapping.
|
|
|
|
|
|
|
|
That means importing `provide` for Dependency Injection and the
|
|
|
|
`Location` and `HashLocationStrategy` symbols from the router,
|
|
|
|
then providing that strategy in the call to `bootstrap`:
|
|
|
|
+makeExample('router/ts/app/boot.2.ts', 'hash-strategy')
|