From ea457825b801df36d1b4816fbc465dde7d794a5e Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Fri, 12 Aug 2016 11:20:44 -0700 Subject: [PATCH] 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. --- public/_includes/_util-fns.jade | 6 +- public/docs/ts/latest/guide/router.jade | 334 ++++++++++++++++-------- 2 files changed, 231 insertions(+), 109 deletions(-) diff --git a/public/_includes/_util-fns.jade b/public/_includes/_util-fns.jade index c36f405145..cf9a569346 100644 --- a/public/_includes/_util-fns.jade +++ b/public/_includes/_util-fns.jade @@ -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='.') diff --git a/public/docs/ts/latest/guide/router.jade b/public/docs/ts/latest/guide/router.jade index 24ac0e5bad..37fefe40f7 100644 --- a/public/docs/ts/latest/guide/router.jade +++ b/public/docs/ts/latest/guide/router.jade @@ -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 . + :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#base-href :marked ### Set the *<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 RouterOutlet ++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 `` tags. @@ -469,8 +483,10 @@ h3#router-outlet RouterOutlet A template may hold exactly one ***unnamed*** ``. The router supports multiple *named* outlets, a feature we'll cover in future. -h3#router-link RouterLink 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 Router Directives 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 Router Directives .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 Router Directives 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 Snapshot: 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 Snapshot: 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 ### 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 - -.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 — which makes it visible everywhere — 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 - +.l-main-section#lifecycle-hooks +:marked ## Router Lifecycle Hooks TODO: Pausing activation -h3#can-activate-guard CanActivate: 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 CanActivate: 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 CanActivate: 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 CanActivate: 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 CanDeactivate: 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 CanDeactivate: 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#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 CanDeactivate: 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 Resolve: 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 Resolve: 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 Resolve: 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 Resolve: pre-fetching component data `) - -.l-main-section +.l-main-section#optional-route-parameters :marked ## Milestone #4: Route Parameters @@ -1500,18 +1587,24 @@ figure.image-display ### 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 — 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 `
  • ` 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#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') .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 - +.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. - -.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 [<base href> element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag in the `` 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)', '')