docs(router): fix markdown, and example mixin cleanup (#2101)

* docs(router): fix markdown and example mixin cleanup

- Fixed: markdown text not under `:marked` region (and so that text was
not showing up in the generated html).
- Fixed: code excerpt title `constructor` -> `isSelected`.
- Cleanup of all makeExample mixin uses.

This cleanup will help make it easier to record differences with the
deprecated router chapter (which must have app-relative makeExample
paths).

* post-review updates

Found another instance of markdown (a heading) outside of a `:marked` region.
This commit is contained in:
Patrice Chalin 2016-08-12 11:20:44 -07:00 committed by Kathy Walrath
parent a771a6e0d0
commit ea457825b8
2 changed files with 231 additions and 109 deletions

View File

@ -98,14 +98,14 @@ mixin makeExample(_filePath, region, _title, stylePatterns)
//- ending is given or is just (), then the title will be suffixed with
//- either "(excerpt)", or "(#{_region})" when _region is defined.
mixin makeExcerpt(_filePath, _region, _title, stylePatterns)
- var matches = _filePath.match(/(.*)\s+\(([\w ]*)\)$/);
- var matches = _filePath.match(/(.*)\s+\(([^\)]*)\)$/);
- var parenText;
- if (matches) { _filePath = matches[1]; parenText = matches[2]; }
- var adjustments = adjustExamplePathAndTitle({filePath:_filePath, title:_title});
- var filePath = adjustments.filePath;
- var title = adjustments.title;
- var region = _region || parenText;
- var excerpt = !region || parenText === '' ? 'excerpt' : parenText || region;
- var region = _region || (_region === '' ? '' : parenText);
- var excerpt = parenText || region || 'excerpt';
- if (title) title = title + ' (' + excerpt + ')';
+makeExample(filePath, region, title, stylePatterns)(format='.')

View File

@ -72,7 +72,8 @@ include ../_util-fns
If the `app` folder is the application root, as it is for our sample application,
set the `href` value *exactly* as shown here.
+makeExample('router/ts/index.1.html','base-href', 'index.html (base href)')(format=".")
+makeExcerpt('index.1.html', 'base-href')
:marked
### Router imports
@ -80,7 +81,8 @@ include ../_util-fns
It is not part of the Angular 2 core. It is in its own library package, `@angular/router`.
We import what we need from it as we would from any other Angular package.
+makeExample('router/ts/app/app.routing.ts','import-router', 'app/app.routing.ts (import)')(format=".")
+makeExcerpt('app/app.routing.ts (import)', 'import-router')
.l-sub-section
:marked
We cover other options in the [details below](#browser-url-styles).
@ -93,7 +95,8 @@ include ../_util-fns
We bootstrap our application with an array of routes that we'll provide to our **`RouterModule.forRoot`** function.
In the following example, we configure our application with four route definitions.
+makeExample('router/ts/app/app.routing.1.ts','route-config','app/app.routing.ts')(format='.')
+makeExcerpt('app/app.routing.1.ts (excerpt)', 'route-config')
.l-sub-section
:marked
@ -122,13 +125,15 @@ include ../_util-fns
a configured *Router* module to our root NgModule imports.
:marked
Next we open `app.module.ts` where we must register our routing, routing providers, and declare our two route components.
+makeExample('router/ts/app/app.module.1.ts','router-basics','app/app.module.ts (basic setup)')(format='.')
+makeExcerpt('app/app.module.1.ts (basic setup)', 'router-basics')
:marked
### Router Outlet
Given this configuration, when the browser URL for this application becomes `/heroes`,
the router matches that URL to the `Route` path `/heroes` and displays the `HeroListComponent`
in a **`RouterOutlet`** that we've placed in the host view's HTML.
code-example(format="", language="html").
code-example(language="html").
<!-- Routed views go here -->
<router-outlet></router-outlet>
:marked
@ -150,7 +155,9 @@ code-example(format="", language="html").
or on its parent element.
We see such bindings in the following `AppComponent` template:
+makeExample('router/ts/app/app.component.1.ts', 'template')(format=".")
+makeExcerpt('app/app.component.1.ts', 'template', '')
.l-sub-section
:marked
We're adding two anchor tags with `RouterLink` and `RouterLinkActive` directives.
@ -257,6 +264,7 @@ table
We gloss over everything in between.
The full source is available in the <live-example></live-example>.
:marked
Our client is the Hero Employment Agency.
Heroes need work and The Agency finds Crises for them to solve.
@ -336,7 +344,7 @@ figure.image-display
figure.image-display
img(src='/resources/images/devguide/router/router-1-anim.gif' alt="App in action" )
<a id="base-href"></a>
a#base-href
:marked
### Set the *&lt;base href>*
The Component Router uses the browser's
@ -356,7 +364,8 @@ figure.image-display
If the `app` folder is the application root, as it is for our application,
set the `href` value in **`index.html`** *exactly* as shown here.
+makeExample('router/ts/index.1.html','base-href', 'index.html (base href)')(format=".")
+makeExcerpt('index.1.html', 'base-href')
.l-sub-section
:marked
HTML 5 style navigation is the Component Router default.
@ -398,7 +407,7 @@ figure.image-display
which returns a module containing the configured `Router` service provider ... and some other,
unseen providers that the routing library requires. We export this as the `routing` token.
+makeExample('router/ts/app/app.routing.2.ts','', 'app/app.routing.ts')(format=".")
+makeExcerpt('app/app.routing.2.ts')
.l-sub-section
:marked
@ -443,7 +452,9 @@ h4#register-providers Register routing in the root NgModule
so they will be registered within our root NgModule.
We also import the `appRoutingProviders` array and add it to the `providers` array.
+makeExample('router/ts/app/app.module.1.ts','', 'app.module.ts')(format=".")
+makeExcerpt('app/app.module.1.ts')
:marked
Providing the router module in our root NgModule makes the Router available everywhere in our application.
@ -457,10 +468,13 @@ figure.image-display
a#shell-template
:marked
The corresponding component template looks like this:
+makeExample('router/ts/app/app.component.1.ts','template')(format=".")
h3#router-outlet <i>RouterOutlet</i>
+makeExcerpt('app/app.component.1.ts', 'template', '')
a#router-outlet
:marked
### *RouterOutlet*
`RouterOutlet` is a component from the router library.
The router displays views within the bounds of the `<router-outlet>` tags.
@ -469,8 +483,10 @@ h3#router-outlet <i>RouterOutlet</i>
A template may hold exactly one ***unnamed*** `<router-outlet>`.
The router supports multiple *named* outlets, a feature we'll cover in future.
h3#router-link <i>RouterLink</i> binding
a#router-link
: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.
@ -506,7 +522,8 @@ h3#router-directives <i>Router Directives</i>
They are readily available for us to use in our template.
:marked
The current state of `app.component.ts` looks like this:
+makeExample('router/ts/app/app.component.1.ts','', 'app/app.component.ts')(format=".")
+makeExcerpt('app/app.component.1.ts')
:marked
### "Getting Started" wrap-up
@ -545,6 +562,7 @@ h3#router-directives <i>Router Directives</i>
.file typings.json
:marked
Here are the files discussed in this milestone
+makeTabs(
`router/ts/app/app.component.1.ts,
router/ts/app/app.module.1.ts,
@ -562,9 +580,10 @@ h3#router-directives <i>Router Directives</i>
crisis-list.component.ts,
index.html`)
h2#heroes-feature Milestone #2: The Heroes Feature
.l-main-section
.l-main-section#heroes-feature
: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
@ -611,7 +630,8 @@ figure.image-display
so its available to all components within our module.
Our `Heroes` module is ready for routing.
+makeExample('router/ts/app/heroes/heroes.module.1.ts','', 'app/heroes/heroes.module.ts')(format=".")
+makeExcerpt('app/heroes/heroes.module.1.ts')
:marked
When we're done organizing, we have four *Hero Management* files:
@ -645,7 +665,9 @@ figure.image-display
We recommend giving each feature area its own route configuration file.
Create a new `heroes.routing.ts` in the `heroes` folder like this:
+makeExample('router/ts/app/heroes/heroes.routing.ts','', 'app/heroes/heroes.routing.ts')(format=".")
+makeExcerpt('app/heroes/heroes.routing.ts')
:marked
We use the same techniques we learned for `app.routing.ts`.
@ -661,12 +683,15 @@ figure.image-display
our feature-specific routes without modifying our main route configuration.
We import our `heroesRouting` token from `heroes.routing.ts` into our `Heroes` module and register the routing.
+makeExample('router/ts/app/heroes/heroes.module.ts', 'heroes-routes', 'app/heroes/heroes.module.ts (Heroes routing)')(format=".")
+makeExcerpt('app/heroes/heroes.module.ts (heroes routing)', 'heroes-routes')
:marked
### Route definition with a parameter
The route to `HeroDetailComponent` has a twist.
+makeExample('router/ts/app/heroes/heroes.routing.ts','hero-detail-route')(format=".")
+makeExcerpt('app/heroes/heroes.routing.ts (excerpt)', 'hero-detail-route', '')
:marked
Notice the `:id` token in 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.
@ -688,8 +713,10 @@ code-example(format="." language="bash").
An [optional-route-parameter](#optional-route-parameter) might be a better choice if we were passing an *optional* value to `HeroDetailComponent`.
h3#navigate Navigate to hero detail imperatively
a#navigate
:marked
### Navigate to hero detail imperatively
*We won't navigate to the detail component by clicking a link*
so we won't be adding a new `RouterLink` anchor tag to the shell.
@ -698,15 +725,21 @@ h3#navigate Navigate to hero detail imperatively
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.1.ts','ctor', 'app/heroes/hero-list.component.ts (Constructor)')(format=".")
+makeExcerpt('app/heroes/hero-list.component.1.ts (constructor)', 'ctor')
:marked
We make a few changes to the template:
+makeExample('router/ts/app/heroes/hero-list.component.1.ts','template')(format=".")
+makeExcerpt('app/heroes/hero-list.component.1.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 `onSelect` method
which we implement as follows:
+makeExample('router/ts/app/heroes/hero-list.component.1.ts','select')(format=".")
+makeExcerpt('app/heroes/hero-list.component.1.ts', 'select', '')
:marked
It calls the router's **`navigate`** method with a **Link Parameters Array**. We can use this same syntax
with a `RouterLink` if we want to use it in HTML rather than code.
@ -718,13 +751,19 @@ h3#route-parameters Setting the route parameters in the list view
Accordingly, the *link parameters array* has *two* items: the **path** of the destination route and a **route parameter** that specifies the
`id` of the selected hero.
+makeExample('router/ts/app/heroes/hero-list.component.1.ts','link-parameters-array')(format=".")
+makeExcerpt('app/heroes/hero-list.component.1.ts', 'link-parameters-array', '')
:marked
The router composes the appropriate two-part destination URL from this array:
code-example(format="." language="bash").
code-example(language="bash").
localhost:3000/hero/15
h3#get-route-parameter Getting the route parameter in the details view
a#get-route-parameter
:marked
### Getting the route parameter in the details view
How does the target `HeroDetailComponent` learn about that `id`?
Certainly not by analyzing the URL! That's the router's job.
@ -735,14 +774,18 @@ a#hero-detail-ctor
:marked
As usual, we write a constructor that asks Angular to inject services
that the component requires and reference them as private variables.
+makeExample('router/ts/app/heroes/hero-detail.component.ts','ctor', 'app/heroes/hero-detail.component.ts (Constructor)')(format=".")
+makeExcerpt('app/heroes/hero-detail.component.ts (constructor)', 'ctor')
:marked
Later, in the `ngOnInit` method,
we use the `ActivatedRoute` service to retrieve the parameters for our route.
Since our parameters are provided as an `Observable`, we _subscribe_ to them for the `id` parameter by name and
tell the `HeroService` to fetch the hero with that `id`. We'll keep a reference to this `Subscription` so we can
tidy things up later.
+makeExample('router/ts/app/heroes/hero-detail.component.ts','ngOnInit')(format=".")
+makeExcerpt('app/heroes/hero-detail.component.ts', 'ngOnInit', '')
.l-sub-section
:marked
Angular calls the `ngOnInit` method shortly after creating an instance of the `HeroDetailComponent`.
@ -759,7 +802,8 @@ a#hero-detail-ctor
*Failure to do so could create a memory leak.*
We unsubscribe from our `Observable` in the `ngOnDestroy` method.
+makeExample('router/ts/app/heroes/hero-detail.component.ts','ngOnDestroy')(format=".")
+makeExcerpt('app/heroes/hero-detail.component.ts', 'ngOnDestroy', '')
.l-sub-section
:marked
@ -801,7 +845,9 @@ h4#snapshot <i>Snapshot</i>: the no-observable alternative
The router offers a *Snapshot* alternative that gives us the initial value of the route parameters.
We don't need to subscribe. We don't have to unsubscribe in `ngOnDestroy`.
It's much simpler to write and read:
+makeExample('router/ts/app/heroes/hero-detail.component.2.ts','snapshot')(format=".")
+makeExcerpt('app/heroes/hero-detail.component.2.ts (excerpt)', 'snapshot', '')
.l-sub-section
:marked
**Remember:** we only get the _initial_ value of the parameters with this technique.
@ -809,15 +855,18 @@ h4#snapshot <i>Snapshot</i>: the no-observable alternative
to this component multiple times in a row.
We are leaving the observable `params` strategy in place just in case.
h3#nav-to-list Navigating back to the list component
a#nav-to-list
: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*
that we can bind to a `[routerLink]` directive.
It holds the **path to the `HeroListComponent`**:
+makeExample('router/ts/app/heroes/hero-detail.component.1.ts','gotoHeroes')(format=".")
+makeExcerpt('app/heroes/hero-detail.component.1.ts (excerpt)', 'gotoHeroes', '')
h3#merge-hero-routes Import hero module into root NgModule
:marked
@ -826,7 +875,7 @@ h3#merge-hero-routes Import hero module into root NgModule
Update `app.module.ts` as follows:
+makeExample('router/ts/app/app.module.2.ts','hero-import', 'app.module.ts (Heroes module import)')(format=".")
+makeExcerpt('app/app.module.2.ts (heroes module import)', 'hero-import')
:marked
We imported the `HeroesModule` and added it to our root NgModule `imports`.
@ -841,7 +890,7 @@ h3#merge-hero-routes Import hero module into root NgModule
Since our `Heroes` routes are defined within our submodule, we can also remove our initial `heroes` route from the `app.routing.ts`.
+makeExample('router/ts/app/app.routing.3.ts','', 'app.routing.ts (v.2)')(format=".")
+makeExcerpt('app/app.routing.3.ts (v2)', '')
:marked
### Heroes App Wrap-up
@ -883,6 +932,7 @@ h3#merge-hero-routes Import hero module into root NgModule
<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.1.ts,
router/ts/app/app.module.2.ts,
@ -901,12 +951,11 @@ h3#merge-hero-routes Import hero module into root NgModule
hero.service.ts,
heroes.module.ts,
heroes.routing.ts`)
:marked
<a id="crisis-center-feature"></a>
.l-main-section
.l-main-section#crisis-center-feature
: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* module.
@ -962,10 +1011,14 @@ h3#merge-hero-routes Import hero module into root NgModule
figure.image-display
img(src='/resources/images/devguide/router/component-tree.png' alt="Component Tree" )
h3#child-routing-component Child Routing Component
a#child-routing-component
:marked
### Child Routing Component
Add the following `crisis-center.component.ts` to the `crisis-center` folder:
+makeExample('router/ts/app/crisis-center/crisis-center.component.ts', 'minus-imports', 'crisis-center/crisis-center.component.ts (minus imports)')(format='.')
+makeExcerpt('app/crisis-center/crisis-center.component.ts (minus imports)', 'minus-imports')
:marked
The `CrisisCenterComponent` is much like the `AppComponent` shell.
@ -981,6 +1034,7 @@ h3#child-routing-component Child Routing Component
Unlike `AppComponent` (and most other components), it **lacks a selector**.
It doesn't need one. We don't *embed* this component in a parent template.
We *navigate* to it from the outside, via the router.
.l-sub-section
:marked
We *can* give it a selector. There's no harm in it.
@ -993,7 +1047,9 @@ h3#child-routing-component Child Routing Component
Instead of registering it with the root NgModule's providers &mdash;
which makes it visible everywhere &mdash;
we register the `CrisisService` in the `CrisisCenterModule` providers array.
+makeExample('router/ts/app/crisis-center/crisis-center.module.1.ts', 'providers')(format='.')
+makeExcerpt('app/crisis-center/crisis-center.module.1.ts', 'providers', '')
:marked
This limits the scope of the `CrisisService` to the *Crisis Center* routes.
No module outside of the *Crisis Center* can access it.
@ -1011,12 +1067,15 @@ h3#child-routing-component Child Routing Component
:marked
### Child Route Configuration
The `CrisisCenterComponent` is a *Routing Component* like the `AppComponent`.
It has its own `RouterOutlet` and its own child routes.
We create a `crisis-center.routing.ts` file as we did the `heroes.routing.ts` file.
But this time we define **child routes** *within* the parent `crisis-center` route.
+makeExample('router/ts/app/crisis-center/crisis-center.routing.1.ts', 'routes', 'app/crisis-center/crisis-center.routing.ts (Routes)' )(format='.')
+makeExcerpt('app/crisis-center/crisis-center.routing.1.ts (Routes)', 'routes')
:marked
Notice that the parent `crisis-center` route has a `children` property
with an array of two routes.
@ -1039,30 +1098,36 @@ h3#child-routing-component Child Routing Component
To write an URL that navigates to the `CrisisDetailComponent`, we'd append the child route path, `/`,
followed by the crisis id, yielding something like:
code-example(format="").
code-example.
localhost:3000/crisis-center/2
:marked
Here's the complete `crisis-center.routing.ts` with its imports.
+makeExample('router/ts/app/crisis-center/crisis-center.routing.1.ts', '', 'app/crisis-center/crisis-center.routing.ts' )(format='.')
+makeExcerpt('app/crisis-center/crisis-center.routing.1.ts', '')
h3#import-crisis-module Import crisis center module into the root NgModule routes
:marked
As with the `Heroes` module, we must import the `Crisis Center` module into the root NgModule:
+makeExample('router/ts/app/app.module.3.ts', '', 'app/app.module.ts (Crisis Center Module)' )(format='.')
+makeExcerpt('app/app.module.3.ts (Crisis Center Module)', '')
:marked
We also remove the initial crisis center route from our `app.routing.ts`. Our routes
are now being provided by our `HeroesModule` and our `CrisisCenter` submodules. We'll keep our `app.routing.ts` file
for general routes which we'll cover later in the chapter.
+makeExample('router/ts/app/app.routing.4.ts', '', 'app/app.routing.ts (v.3)' )(format='.')
+makeExcerpt('app/app.routing.4.ts (v3)', '')
a#redirect
h3#redirect Redirecting routes
:marked
### Redirecting routes
When the application launches, the initial URL in the browser bar is something like:
code-example(format="").
code-example.
localhost:3000
:marked
That doesn't match any of our configured routes which means that our application won't display any component when it's launched.
The user must click one of the navigation links to trigger a navigation and display something.
@ -1072,7 +1137,8 @@ code-example(format="").
The preferred solution is to add a `redirect` route that transparently translates from the initial relative URL (`''`)
to the desired default path (`/crisis-center`):
+makeExample('router/ts/app/crisis-center/crisis-center.routing.2.ts', 'redirect', 'app/crisis-center/crisis-center.routing.ts (redirect route)' )(format='.')
+makeExcerpt('app/crisis-center/crisis-center.routing.2.ts' , 'redirect', '')
:marked
A redirect route requires a `pathMatch` property to tell the router how to match a URL to the path of a route.
@ -1102,7 +1168,8 @@ code-example(format="").
:marked
The updated route definitions look like this:
+makeExample('router/ts/app/crisis-center/crisis-center.routing.2.ts', 'routes', 'app/crisis-center/crisis-center.routing.ts (Routes v.2)' )(format='.')
+makeExcerpt('app/crisis-center/crisis-center.routing.2.ts (routes v2)' , 'routes')
.l-main-section
h2#guards Route Guards
@ -1153,15 +1220,16 @@ h2#guards Route Guards
Let's look at some examples.
.l-main-section
// :marked
<a id="lifecycle-hooks"></a>
.l-main-section#lifecycle-hooks
:marked
## Router Lifecycle Hooks
TODO: Pausing activation
h3#can-activate-guard <i>CanActivate</i>: requiring authentication
a#can-activate-guard
:marked
### *CanActivate*: requiring authentication
Applications often restrict access to a feature area based on who the user is.
We could permit access only to authenticated users or to users with a specific role.
We might block or limit access until the user's account is activated.
@ -1173,13 +1241,17 @@ h3#can-activate-guard <i>CanActivate</i>: requiring authentication
We intend to extend the Crisis Center with some new *administrative* features.
Those features aren't defined yet. So we add the following placeholder component.
+makeExample('router/ts/app/crisis-center/crisis-admin.component.1.ts', '', 'crisis-admin.component.ts')(format=".")
+makeExcerpt('app/crisis-center/crisis-admin.component.1.ts')
:marked
Next, we add a child route to the `crisis-center.routes` with the path, `/admin`.
+makeExample('router/ts/app/crisis-center/crisis-center.routing.3.ts', 'admin-route-no-guard', 'crisis-center.routing.ts (admin route)')(format=".")
+makeExcerpt('app/crisis-center/crisis-center.routing.3.ts (admin route)', 'admin-route-no-guard')
:marked
And we add a link to the `AppComponent` shell that users can click to get to this feature.
+makeExample('router/ts/app/app.component.4.ts', 'template', 'app/app.component.ts (template)')(format=".")
+makeExcerpt('app/app.component.4.ts', 'template')
.l-sub-section
:marked
@ -1202,19 +1274,26 @@ h3#can-activate-guard <i>CanActivate</i>: requiring authentication
At the moment we're interested in seeing how guards work so our first version does nothing useful.
It simply logs to console and `returns` true immediately, allowing navigation to proceed:
+makeExample('router/ts/app/auth-guard.service.1.ts', '', 'app/auth-guard.service.ts')(format=".")
+makeExcerpt('app/auth-guard.service.1.ts')
:marked
Next we open `crisis-center.routing.ts `, import the `AuthGuard` class, and
update the admin route with a `CanActivate` guard property that references it:
+makeExample('router/ts/app/crisis-center/crisis-center.routing.ts', 'admin-route', 'crisis-center.routing.ts (guarded admin route)')(format=".")
Our admin feature is now protected by the guard, albeit protected poorly.
+makeExcerpt('app/crisis-center/crisis-center.routing.ts (guarded admin route)', 'admin-route')
:marked
Our admin feature is now protected by the guard, albeit protected poorly.
#### Teach *AuthGuard* to authenticate
Let's make our `AuthGuard` at least pretend to authenticate.
The `AuthGuard` should call an application service that can login a user and retain information about the current user.
Here's a demo `AuthService`:
+makeExample('router/ts/app/auth.service.ts', '', 'app/auth.service.ts')(format=".")
+makeExcerpt('app/auth.service.ts')
:marked
Although it doesn't actually log in, it has what we need for this discussion.
It has an `isLoggedIn` flag to tell us whether the user is authenticated.
@ -1222,7 +1301,9 @@ h3#can-activate-guard <i>CanActivate</i>: requiring authentication
The `redirectUrl` property will store our attempted URL so we can navigate to it after authenticating.
Let's revise our `AuthGuard` to call it.
+makeExample('router/ts/app/auth-guard.service.2.ts', '', 'app/auth-guard.service.ts (v.2)')(format=".")
+makeExcerpt('app/auth-guard.service.2.ts (v2)', '')
:marked
Notice that we *inject* the `AuthService` and the `Router` in the constructor.
We haven't provided the `AuthService` yet but it's good to know that we can inject helpful services into our routing guards.
@ -1295,7 +1376,9 @@ h3#can-deactivate-guard <i>CanDeactivate</i>: handling unsaved changes
We discard the changes if the user presses he *Cancel* button.
Both buttons navigate back to the crisis list after save or cancel.
+makeExample('router/ts/app/crisis-center/crisis-detail.component.1.ts', 'cancel-save', 'crisis-detail.component.ts (excerpt)')(format=".")
+makeExcerpt('app/crisis-center/crisis-detail.component.1.ts (excerpt)', 'cancel-save')
:marked
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.
@ -1318,24 +1401,27 @@ h3#can-deactivate-guard <i>CanDeactivate</i>: handling unsaved changes
*resolves* when the user eventually decides what to do: either
to discard changes and navigate away (`true`) or to preserve the pending changes and stay in the crisis editor (`false`).
<a id="CanDeactivate"></a>
a#CanDeactivate
:marked
We create a `Guard` that will check for the presence of a `canDeactivate` function in our component, in this
case being `CrisisDetailComponent`. We don't need to know the details of how our `CrisisDetailComponent` confirms deactivation.
This makes our guard reusable, which is an easy win for us.
+makeExample('router/ts/app/can-deactivate-guard.service.ts', '', 'can-deactivate-guard.service.ts')
+makeExample('app/can-deactivate-guard.service.ts')
:marked
Alternatively, We could make a component-specific `CanDeactivate` guard for our `CrisisDetailComponent`. The `canDeactivate` method provides us
with the current instance of our `component`, the current `ActivatedRoute` and `RouterStateSnapshot` in case we needed to access
some external information. This would be useful if we only wanted to use this guard for this component and needed to ask the component's
properties in or to confirm whether the router should allow navigation away from it.
+makeExample('router/ts/app/can-deactivate-guard.service.1.ts', '', 'can-deactivate-guard.service.ts (component-specific)')
+makeExcerpt('app/can-deactivate-guard.service.1.ts (component-specific)', '')
:marked
Looking back at our `CrisisDetailComponent`, we have implemented our confirmation workflow for unsaved changes.
+makeExample('router/ts/app/crisis-center/crisis-detail.component.1.ts', 'cancel-save-only', 'crisis-detail.component.ts (excerpt)')
+makeExcerpt('app/crisis-center/crisis-detail.component.1.ts (excerpt)', 'cancel-save-only')
:marked
Notice that the `canDeactivate` method *can* return synchronously;
it returns `true` immediately if there is no crisis or there are no pending changes.
@ -1344,11 +1430,13 @@ h3#can-deactivate-guard <i>CanDeactivate</i>: handling unsaved changes
:marked
We add the `Guard` to our crisis detail route in `crisis-center.routing.ts` using the `canDeactivate` array.
+makeExample('router/ts/app/crisis-center/crisis-center.routing.4.ts', '', 'crisis-center.routing.ts')
+makeExample('app/crisis-center/crisis-center.routing.4.ts', '')
:marked
We also need to add the `Guard` to our main `appRoutingProviders` so the `Router` can inject it during the navigation process.
+makeExample('router/ts/app/app.routing.5.ts', '', 'app.routing.ts')
+makeExample('app/app.routing.5.ts', '', '')
:marked
Now we have given our user a safeguard against unsaved changes.
@ -1387,7 +1475,7 @@ h3#resolve-guard <i>Resolve</i>: pre-fetching component data
Let's create our `crisis-detail-resolve.service.ts` file within our `Crisis Center` feature area.
+makeExample('router/ts/app/crisis-center/crisis-detail-resolve.service.ts', '', 'crisis-detail-resolve.service.ts')
+makeExample('app/crisis-center/crisis-detail-resolve.service.ts', '')
:marked
We'll take the relevant parts of the `ngOnInit` lifecycle hook in our `CrisisDetailComponent` and moved them into our `CrisisDetailResolve` guard.
@ -1401,12 +1489,12 @@ h3#resolve-guard <i>Resolve</i>: pre-fetching component data
Now that our guard is ready, we'll import it in our `crisis-center.routing.ts` and use the `resolve` object in our route configuration.
+makeExample('router/ts/app/crisis-center/crisis-center.routing.5.ts', 'crisis-detail-resolve', 'crisis-center.routing.ts (resolve)')
+makeExcerpt('app/crisis-center/crisis-center.routing.5.ts (resolve)', 'crisis-detail-resolve')
:marked
We'll add the `CrisisDetailResolve` service to our crisis center module's `providers`, so its available to the `Router` during the navigation process.
+makeExample('router/ts/app/crisis-center/crisis-center.module.ts', 'crisis-detail-resolve', 'crisis-center.module.ts (crisis detail resolve provider)')
+makeExcerpt('app/crisis-center/crisis-center.module.ts (crisis detail resolve provider)', 'crisis-detail-resolve')
:marked
Now that we've added our `Resolve` guard to fetch data before the route loads, we no longer need to do this once we get into our `CrisisDetailComponent`.
@ -1414,7 +1502,7 @@ h3#resolve-guard <i>Resolve</i>: pre-fetching component data
Once activated, all we need to do is set our local `crisis` and `editName` properties from our resolved `Crisis` information. We no longer need to subscribe
and unsubscribe to the `ActivatedRoute` params to fetch the `Crisis` because it is being provided synchronously at the time the route component is activated.
+makeExample('router/ts/app/crisis-center/crisis-detail.component.ts', 'crisis-detail-resolve', 'crisis-detail.component.ts (ngOnInit v.2)')
+makeExcerpt('app/crisis-center/crisis-detail.component.ts (ngOnInit v2)', 'crisis-detail-resolve')
:marked
**Two critical points**
@ -1455,8 +1543,7 @@ h3#resolve-guard <i>Resolve</i>: pre-fetching component data
`)
<a id="optional-route-parameters"></a>
.l-main-section
.l-main-section#optional-route-parameters
:marked
## Milestone #4: Route Parameters
@ -1500,18 +1587,24 @@ figure.image-display
<a id="route-parameters-object"></a>
### Route parameter
When navigating to the `HeroDetailComponent` we specified the `id` of the hero-to-edit in the
*route parameter* and made it the second item of the [*link parameters array*](#link-parameters-array).
+makeExample('router/ts/app/heroes/hero-list.component.1.ts','link-parameters-array')(format=".")
+makeExcerpt('app/heroes/hero-list.component.1.ts', 'link-parameters-array', '')
:marked
The router embedded the `id` value in the navigation URL because we had defined it
as a route parameter with an `:id` placeholder token in the route `path`:
+makeExample('router/ts/app/heroes/heroes.routing.ts','hero-detail-route')(format=".")
+makeExcerpt('app/heroes/heroes.routing.ts', 'hero-detail-route', '')
:marked
When the user clicks the back button, the `HeroDetailComponent` constructs another *link parameters array*
which it uses to navigate back to the `HeroListComponent`.
+makeExample('router/ts/app/heroes/hero-detail.component.1.ts','gotoHeroes')(format=".")
+makeExcerpt('app/heroes/hero-detail.component.1.ts', 'gotoHeroes', '')
:marked
This array lacks a route parameter because we had no reason to send information to the `HeroListComponent`.
@ -1521,7 +1614,9 @@ figure.image-display
We do that with an object that contains our optional `id` parameter.
We also defined a junk parameter (`foo`) that the `HeroListComponent` should ignore.
Here's the revised navigation statement:
+makeExample('router/ts/app/heroes/hero-detail.component.ts','gotoHeroes-navigate')(format=".")
+makeExcerpt('app/heroes/hero-detail.component.ts', 'gotoHeroes-navigate', '')
:marked
The application still works. Clicking "back" returns to the hero list view.
@ -1532,15 +1627,18 @@ figure.image-display
When running in plunker, pop out the preview window by clicking the blue 'X' button in the upper right corner.
:marked
It should look something like this, depending on where you run it:
code-example(format="." language="bash").
code-example(language="bash").
localhost:3000/heroes;id=15;foo=foo
:marked
The `id` value appears in the URL as (`;id=15;foo=foo`), not in the URL path.
The path for the "Heroes" route doesn't have an `:id` token.
:marked
The optional route parameters are not separated by "?" and "&".
They are **separated by semicolons (;)**
This is *matrix URL* notation &mdash; something we may not have seen before.
.l-sub-section
:marked
*Matrix URL* notation is an idea first floated
@ -1575,23 +1673,31 @@ code-example(format="." language="bash").
This time we'll be navigating in the opposite direction, from the `HeroDetailComponent` to the `HeroListComponent`.
First we extend the router import statement to include the `ActivatedRoute` service symbol;
+makeExample('router/ts/app/heroes/hero-list.component.ts','import-router', 'hero-list.component.ts (import)')(format=".")
+makeExcerpt('app/heroes/hero-list.component.ts (import)', 'import-router')
:marked
Then we use the `ActivatedRoute` to access the `params` _Observable_ so we can subscribe
and extract the `id` parameter as the `selectedId`:
+makeExample('router/ts/app/heroes/hero-list.component.ts','ctor', 'hero-list.component.ts (constructor)')(format=".")
+makeExcerpt('app/heroes/hero-list.component.ts (constructor)', 'ctor')
.l-sub-section
:marked
All route/query parameters are strings.
The (+) in front of the `params['id']` expression is a JavaScript trick to convert the string to an integer.
:marked
We add an `isSelected` method that returns true when a hero's id matches the selected id.
+makeExample('router/ts/app/heroes/hero-list.component.ts','isSelected', 'hero-list.component.ts (constructor)')(format=".")
+makeExcerpt('app/heroes/hero-list.component.ts', 'isSelected')
:marked
Finally, we update our template with a [Class Binding](template-syntax.html#class-binding) to that `isSelected` method.
The binding adds the `selected` CSS class when the method returns `true` and removes it when `false`.
Look for it within the repeated `<li>` tag as shown here:
+makeExample('router/ts/app/heroes/hero-list.component.ts','template', 'hero-list.component.ts (template)')(format=".")
+makeExcerpt('app/heroes/hero-list.component.ts', 'template')
:marked
When the user navigates from the heroes list to the "Magneta" hero and back, "Magneta" appears selected:
figure.image-display
@ -1599,11 +1705,11 @@ figure.image-display
:marked
The optional `foo` route parameter is harmless and continues to be ignored.
<a id="query-parameters"></a>
<a id="fragment"></a>
a#query-parameters
a#fragment
:marked
### Query Parameters and Fragments
:marked
In our [route parameters](#optional-route-parameters) example, we only dealt with parameters specific to
our route, but what if we wanted optional parameters available to all routes? This is where our
query parameters come into play and serve a special purpose in our application.
@ -1616,7 +1722,8 @@ figure.image-display
We'll also provide an arbitrary `anchor` fragment, which we would use to jump to a certain point on our page.
We'll add the `NavigationExtras` object to our `router.navigate` method that navigates us to our `/login` route.
+makeExample('router/ts/app/auth-guard.service.ts','', 'auth-guard.service.ts (v.3)')
+makeExcerpt('app/auth-guard.service.ts (v3)', '')
:marked
We can also **preserve** query parameters and fragments across navigations without having to re-provide them
@ -1624,13 +1731,14 @@ figure.image-display
and provide the `preserveQueryParams` and `preserveFragment` to pass along the current query parameters
and fragment to the next route.
+makeExample('router/ts/app/login.component.ts','preserve', 'login.component.ts (preserved)')(format=".")
+makeExcerpt('app/login.component.ts', 'preserve')
:marked
Since we'll be navigating to our *Crisis Admin* route after logging in, we'll update it to handle our
query parameters and fragment.
+makeExample('router/ts/app/crisis-center/crisis-admin.component.ts','', 'crisis-admin.component.ts (v.2)')
+makeExcerpt('app/crisis-center/crisis-admin.component.ts (v2)', '')
:marked
*Query Parameters* and *Fragments* are also available through the `ActivatedRoute` service available to route components.
Just like our *route parameters*, query parameters and fragments are provided as an `Observable`.
@ -1696,7 +1804,7 @@ figure.image-display
out our `Crisis Center` feature area. After the path to the file we use a `#` to denote where our file path ends and to tell the `Router` the name
of our `CrisisCenter` NgModule. If we look in our `crisis-center.module` file, we can see it matches name of our exported NgModule class.
+makeExample('router/ts/app/crisis-center/crisis-center.module.ts', 'crisis-center-module-export', 'crisis-center.module.ts (export)')
+makeExcerpt('app/crisis-center/crisis-center.module.ts (export)', 'crisis-center-module-export')
:marked
The `loadChildren` property is used by the `Router` to map to our bundle we want to lazy-load, in this case being the `CrisisCenterModule`.
@ -1714,13 +1822,13 @@ figure.image-display
to break our `CrisisCenterModule` into a completely separate module. In our `app.module.ts`, we'll remove our `CrisisCenterModule` from the
`imports` array since we'll be loading it on-demand an we'll remove the imported `CrisisCenterModule`.
+makeExample('router/ts/app/app.module.ts', '', 'app.module.ts (final)')
+makeExcerpt('app/app.module.ts (final)', '')
:marked
If our initial redirect went to `/heroes` instead of going to `/crisis-center`, the `CrisisCenterModule` would not be loaded until the user
visited a `Crisis Center` route. We'll update our redirect in our `app.routing.ts` to make this change.
+makeExample('router/ts/app/app.routing.6.ts', 'heroes-redirect', 'app.routing.ts (heroes redirect)')
+makeExcerpt('app/app.routing.6.ts (heroes redirect)', 'heroes-redirect')
<a id="final-app"></a>
.l-main-section
@ -1739,10 +1847,10 @@ figure.image-display
The appendix material isn't essential. Continued reading is for the curious.
.l-main-section
<a id="link-parameters-array"></a>
.l-main-section#link-parameters-array
:marked
## Appendix: Link Parameters Array
We've mentioned the *Link Parameters Array* several times. We've used it several times.
A link parameters array holds the ingredients for router navigation:
@ -1750,19 +1858,27 @@ figure.image-display
* required and optional route parameters that go into the route URL
We can bind the `RouterLink` directive to such an array like this:
+makeExample('router/ts/app/app.component.3.ts', 'h-anchor')(format=".")
+makeExcerpt('app/app.component.3.ts', 'h-anchor', '')
:marked
We've written a two element array when specifying a route parameter like this
+makeExample('router/ts/app/heroes/hero-list.component.1.ts', 'nav-to-detail')(format=".")
+makeExcerpt('app/heroes/hero-list.component.1.ts', 'nav-to-detail', '')
:marked
We can provide optional route parameters in an object like this:
+makeExample('router/ts/app/app.component.3.ts', 'cc-query-params')(format=".")
+makeExcerpt('app/app.component.3.ts', 'cc-query-params', '')
:marked
These three examples cover our needs for an app with one level routing.
The moment we add a child router, such as the *Crisis Center*, we create new link array possibilities.
Recall that we specified a default child route for *Crisis Center* so this simple `RouterLink` is fine.
+makeExample('router/ts/app/app.component.3.ts', 'cc-anchor-w-default')(format=".")
+makeExcerpt('app/app.component.3.ts', 'cc-anchor-w-default', '')
:marked
Let's parse it out.
* The first item in the array identifies the parent route ('/crisis-center').
@ -1782,17 +1898,20 @@ figure.image-display
* We add `id` of the *Dragon Crisis* as the second item in the array (`1`)
It looks like this!
+makeExample('router/ts/app/app.component.3.ts', 'Dragon-anchor')(format=".")
+makeExcerpt('app/app.component.3.ts', 'Dragon-anchor', '')
:marked
If we wanted to, we could redefine our `AppComponent` template with *Crisis Center* routes exclusively:
+makeExample('router/ts/app/app.component.3.ts', 'template')(format=".")
+makeExcerpt('app/app.component.3.ts', 'template', '')
:marked
In sum, we can write applications with one, two or more levels of routing.
The link parameters array affords the flexibility to represent any routing depth and
any legal sequence of route paths, (required) router parameters and (optional) route parameter objects.
<a id="onInit"></a>
.l-main-section
.l-main-section#onInit
:marked
## Appendix: Why use an *ngOnInit* method
@ -1890,7 +2009,9 @@ code-example(format=".", language="bash").
The preferred way to configure the strategy is to add a
[&lt;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.1.html','base-href')(format=".")
+makeExcerpt('index.1.html', 'base-href', '')
:marked
Without that tag, the browser may not be able to load resources
(images, css, scripts) when "deep linking" into the app.
@ -1914,4 +2035,5 @@ code-example(format=".", language="bash").
We can go old-school with the `HashLocationStrategy` by
providing the `useHash: true` in an object as the second argument of the `RouterModule.forRoot`
in our root NgModule.
+makeExample('router/ts/app/app.module.4.ts','', 'app.module.ts (hash URL strategy)')
+makeExcerpt('app/app.module.4.ts (hash URL strategy)', '')