From ce174a26fb2be325e698954603f79ff910b17cef Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Sun, 1 Jan 2017 00:25:52 -0800 Subject: [PATCH] docs(router): chalin router copyedits #3054 (#3060) * docs(router): chalin copyedits * docs(router): bell copy edits + routing module order & inspect config --- .../router/ts/app/app-routing.module.2.ts | 4 +- .../router/ts/app/app-routing.module.ts | 4 +- .../router/ts/app/app.component.4.ts | 6 +- .../_examples/router/ts/app/app.module.3.ts | 2 + .../_examples/router/ts/app/app.module.ts | 13 +- .../ts/app/crisis-center/crisis.service.ts | 9 +- .../router/ts/app/heroes/heroes.module.1.ts | 25 - .../router/ts/app/heroes/heroes.module.ts | 13 +- public/docs/_examples/router/ts/index.html | 2 +- public/docs/_examples/toh-4/ts/plnkr.json | 2 +- public/docs/ts/latest/guide/router.jade | 701 ++++++++++-------- .../devguide/router/shell-and-outlet.png | Bin 11643 -> 4683 bytes 12 files changed, 432 insertions(+), 349 deletions(-) delete mode 100644 public/docs/_examples/router/ts/app/heroes/heroes.module.1.ts diff --git a/public/docs/_examples/router/ts/app/app-routing.module.2.ts b/public/docs/_examples/router/ts/app/app-routing.module.2.ts index d9a8fcaebb..42ac84a481 100644 --- a/public/docs/_examples/router/ts/app/app-routing.module.2.ts +++ b/public/docs/_examples/router/ts/app/app-routing.module.2.ts @@ -1,14 +1,14 @@ -// #docplaster // #docregion -// #docregion v2 import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { CrisisListComponent } from './crisis-list.component'; +// import { HeroListComponent } from './hero-list.component'; // <-- delete this line import { PageNotFoundComponent } from './not-found.component'; const appRoutes: Routes = [ { path: 'crisis-center', component: CrisisListComponent }, + // { path: 'heroes', component: HeroListComponent }, // <-- delete this line { path: '', redirectTo: '/heroes', pathMatch: 'full' }, { path: '**', component: PageNotFoundComponent } ]; diff --git a/public/docs/_examples/router/ts/app/app-routing.module.ts b/public/docs/_examples/router/ts/app/app-routing.module.ts index 0748006655..cc01ced890 100644 --- a/public/docs/_examples/router/ts/app/app-routing.module.ts +++ b/public/docs/_examples/router/ts/app/app-routing.module.ts @@ -8,7 +8,7 @@ import { PageNotFoundComponent } from './not-found.component'; import { CanDeactivateGuard } from './can-deactivate-guard.service'; import { AuthGuard } from './auth-guard.service'; -import { SelectivePreloadingStrategy } from './selective-preloading-strategy'; +import { SelectivePreloadingStrategy } from './selective-preloading-strategy'; const appRoutes: Routes = [ { @@ -47,4 +47,4 @@ const appRoutes: Routes = [ SelectivePreloadingStrategy ] }) -export class AppRoutingModule {} +export class AppRoutingModule { } diff --git a/public/docs/_examples/router/ts/app/app.component.4.ts b/public/docs/_examples/router/ts/app/app.component.4.ts index 8d0e706e51..a630703c28 100644 --- a/public/docs/_examples/router/ts/app/app.component.4.ts +++ b/public/docs/_examples/router/ts/app/app.component.4.ts @@ -9,9 +9,9 @@ import { Component } from '@angular/core'; // #docregion outlets diff --git a/public/docs/_examples/router/ts/app/app.module.3.ts b/public/docs/_examples/router/ts/app/app.module.3.ts index 08f4579f02..862faf1c51 100644 --- a/public/docs/_examples/router/ts/app/app.module.3.ts +++ b/public/docs/_examples/router/ts/app/app.module.3.ts @@ -11,12 +11,14 @@ import { CrisisListComponent } from './crisis-list.component'; import { PageNotFoundComponent } from './not-found.component'; @NgModule({ +// #docregion module-imports imports: [ BrowserModule, FormsModule, HeroesModule, AppRoutingModule ], +// #enddocregion module-imports declarations: [ AppComponent, CrisisListComponent, diff --git a/public/docs/_examples/router/ts/app/app.module.ts b/public/docs/_examples/router/ts/app/app.module.ts index 7164f6fa32..d580964e52 100644 --- a/public/docs/_examples/router/ts/app/app.module.ts +++ b/public/docs/_examples/router/ts/app/app.module.ts @@ -1,8 +1,12 @@ +// #docplaster // #docregion import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; +// #docregion inspect-config +import { Router } from '@angular/router'; +// #enddocregion inspect-config import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; @@ -33,4 +37,11 @@ import { DialogService } from './dialog.service'; ], bootstrap: [ AppComponent ] }) -export class AppModule { } +// #docregion inspect-config +export class AppModule { + // Diagnostic only: inspect router configuration + constructor(router: Router) { + console.log('Routes: ', JSON.stringify(router.config, undefined, 2)); + } +} +// #enddocregion inspect-config diff --git a/public/docs/_examples/router/ts/app/crisis-center/crisis.service.ts b/public/docs/_examples/router/ts/app/crisis-center/crisis.service.ts index b5d9e282c0..99272fe767 100644 --- a/public/docs/_examples/router/ts/app/crisis-center/crisis.service.ts +++ b/public/docs/_examples/router/ts/app/crisis-center/crisis.service.ts @@ -1,5 +1,5 @@ // #docplaster -// #docregion +// #docregion , mock-crises export class Crisis { constructor(public id: number, public name: string) { } } @@ -10,6 +10,7 @@ const CRISES = [ new Crisis(3, 'Giant Asteroid Heading For Earth'), new Crisis(4, 'Procrastinators Meeting Delayed Again'), ]; +// #enddocregion mock-crises let crisesPromise = Promise.resolve(CRISES); @@ -28,8 +29,7 @@ export class CrisisService { .then(crises => crises.find(crisis => crisis.id === +id)); } -// #enddocregion - + // #enddocregion addCrisis(name: string) { name = name.trim(); if (name) { @@ -37,6 +37,5 @@ export class CrisisService { crisesPromise.then(crises => crises.push(crisis)); } } -// #docregion + // #docregion } -// #enddocregion diff --git a/public/docs/_examples/router/ts/app/heroes/heroes.module.1.ts b/public/docs/_examples/router/ts/app/heroes/heroes.module.1.ts deleted file mode 100644 index 973102f83b..0000000000 --- a/public/docs/_examples/router/ts/app/heroes/heroes.module.1.ts +++ /dev/null @@ -1,25 +0,0 @@ -// #docregion -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; - -import { HeroListComponent } from './hero-list.component'; -import { HeroDetailComponent } from './hero-detail.component'; - -import { HeroService } from './hero.service'; - -@NgModule({ - imports: [ - CommonModule, - FormsModule - ], - declarations: [ - HeroListComponent, - HeroDetailComponent - ], - providers: [ - HeroService - ] -}) -export class HeroesModule {} -// #enddocregion diff --git a/public/docs/_examples/router/ts/app/heroes/heroes.module.ts b/public/docs/_examples/router/ts/app/heroes/heroes.module.ts index 72212dbd24..95ee64a182 100644 --- a/public/docs/_examples/router/ts/app/heroes/heroes.module.ts +++ b/public/docs/_examples/router/ts/app/heroes/heroes.module.ts @@ -1,4 +1,6 @@ +// #docplaster // #docregion +// #docregion v1 import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; @@ -8,23 +10,24 @@ import { HeroDetailComponent } from './hero-detail.component'; import { HeroService } from './hero.service'; -// #docregion heroes-routes +// #enddocregion v1 import { HeroRoutingModule } from './heroes-routing.module'; +// #docregion v1 @NgModule({ imports: [ CommonModule, FormsModule, +// #enddocregion v1 HeroRoutingModule +// #docregion v1 ], declarations: [ HeroListComponent, HeroDetailComponent ], - providers: [ - HeroService - ] + providers: [ HeroService ] }) -// #enddocregion heroes-routes export class HeroesModule {} +// #enddocregion v1 // #enddocregion diff --git a/public/docs/_examples/router/ts/index.html b/public/docs/_examples/router/ts/index.html index 93114d517b..f5298e30d9 100644 --- a/public/docs/_examples/router/ts/index.html +++ b/public/docs/_examples/router/ts/index.html @@ -6,7 +6,7 @@ - Router Sample + Angular Router diff --git a/public/docs/_examples/toh-4/ts/plnkr.json b/public/docs/_examples/toh-4/ts/plnkr.json index cb495e519e..1b3e442ac0 100644 --- a/public/docs/_examples/toh-4/ts/plnkr.json +++ b/public/docs/_examples/toh-4/ts/plnkr.json @@ -3,7 +3,7 @@ "files":[ "!**/*.d.ts", "!**/*.js", - "!**/*.[1].*" + "!**/*.[1,2].*" ], "tags": ["tutorial", "tour", "heroes"] } diff --git a/public/docs/ts/latest/guide/router.jade b/public/docs/ts/latest/guide/router.jade index 2ffa94f1b3..4ffc9d4c7f 100644 --- a/public/docs/ts/latest/guide/router.jade +++ b/public/docs/ts/latest/guide/router.jade @@ -14,10 +14,11 @@ include ../../../_includes/_see-addr-bar ## Overview The browser is a familiar model of application navigation: + * Enter a URL in the address bar and the browser navigates to a corresponding page. * Click links on the page and the browser navigates to a new page. * Click the browser's back and forward buttons and the browser navigates - backward and forward through the history of pages you've seen. + backward and forward through the history of pages you've seen. The Angular `Router` ("the router") borrows from this model. It can interpret a browser URL as an instruction to navigate to a client-generated view. @@ -46,6 +47,7 @@ include ../../../_includes/_see-addr-bar * Embedding critical information in the URL with [route parameters](#route-parameters) * Providing non-critical information in [optional route parameters](#optional-route-parameters) * Refactoring routing into a [routing module](#routing-module) + * [Importing routing modules in the proper order](#routing-module-order) * Add [child routes](#child-routing-component) under a feature section * [Grouping child routes](#component-less-route) without a component * Displaying [multiple routes](#named-outlets) in separate outlets @@ -60,6 +62,7 @@ include ../../../_includes/_see-addr-bar * Loading feature areas [asynchronously](#asynchronous-routing) * Preloading feature areas [during navigation](#preloading) * Using a [custom strategy](#custom-preloading) to only preload certain features + * [Inspect the router's configuration](#inspect-config). * Choosing the "HTML5" or "hash" [URL style](#browser-url-styles) .l-main-section @@ -142,7 +145,7 @@ a#example-config The wildcard route comes last because it matches _every URL_ and should be selected _only_ if no other routes are matched first. :marked - ### Router Outlet + ### 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` @@ -153,7 +156,7 @@ code-example(language="html"). <!-- Routed views go here --> :marked - ### Router Links + ### Router links Now you have routes configured and a place to render them, but how do you navigate? The URL could arrive directly from the browser address bar. @@ -176,7 +179,7 @@ code-example(language="html"). You can add this directive to the anchor or to its parent element. :marked - ### Router State + ### Router state After the end of each successful navigation lifecycle, the router builds a tree of `ActivatedRoute` objects that make up the current state of the router. You can access the current `RouterState` from anywhere in the @@ -225,7 +228,7 @@ table td RouterLink td. The directive for binding a clickable HTML element to - a route. Clicking an anchor tag with a routerLink directive + a route. Clicking an element with a routerLink directive that is bound to a string or a link parameters array triggers a navigation. tr td RouterLinkActive @@ -243,19 +246,19 @@ table The current state of the router including a tree of the currently activated routes together with convenience methods for traversing the route tree. tr - td Link Parameters Array + td Link parameters array td. An array that the router interprets as a routing instruction. You can bind that array to a RouterLink or pass the array as an argument to the Router.navigate method. tr - td Routing Component + td Routing component td. An Angular component with a RouterOutlet that displays views based on router navigations. .l-main-section :marked - ## The Sample Application + ## The sample application This guide describes development of a multi-page routed sample application. Along the way, it highlights design decisions and describes key features of the router such as: @@ -272,7 +275,7 @@ table * the `CanLoad` guard (check before loading feature module assets) The guide proceeds as a sequence of milestones as if you were building the app step-by-step. - But it is not a tutorial and it glosses over details of Angular application construction + But, it is not a tutorial and it glosses over details of Angular application construction that are more thoroughly covered elsewhere in the documentation. The full source for the final version of the app can be seen and downloaded from the . @@ -284,8 +287,9 @@ table Heroes need work and the agency finds crises for them to solve. The application has three main feature areas: + 1. A *Crisis Center* for maintaining the list of crises for assignment to heroes. - 1. A *Heroes* area for maintaining the list of heroes employed by The Agency. + 1. A *Heroes* area for maintaining the list of heroes employed by the agency. 1. An *Admin* area to manage the list of crises and heroes. Try it by clicking on this live example link. @@ -305,7 +309,7 @@ figure.image-display Notice that the name change took effect immediately. Had you clicked the browser's back button instead of the "Back" button, - the app would have returned you to the heroes List as well. + the app would have returned you to the heroes list as well. Angular app navigation updates the browser history as normal web navigation does. Now click the *Crisis Center* link for a list of ongoing crises. @@ -349,7 +353,7 @@ figure.image-display .l-main-section#getting-started :marked - ## Milestone #1: Getting Started with the Router + ## Milestone 1: Getting started with the router Begin with a simple version of the app that navigates between two empty views. figure.image-display @@ -360,7 +364,7 @@ a#base-href ### Set the *<base href>* The router uses the browser's - [history.pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries) + history.pushState for navigation. Thanks to `pushState`, you can make in-app URL paths look the way you want them to look, e.g. `localhost:3000/crisis-center`. The in-app URLs can be indistinguishable from server URLs. @@ -369,15 +373,17 @@ a#base-href .l-sub-section :marked - HTML 5 style navigation is the 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. + HTML 5 style navigation is the router default. + In the [Browser URL Styles](#browser-url-styles) Appendix, + learn why HTML 5 style is preferred, how to adjust its behavior, and how to switch to the + older hash (#) style, if necessary. :marked - You must **add a [<base href> element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag** - to the `index.html` to make `pushState` routing work. - The browser also needs the base `href` value to prefix *relative* URLs when downloading and linking to - css files, scripts, and images. + You must **add a + <base href> element** + to the app's `index.html` for `pushState` routing to work. + The browser uses the base `href` value to prefix *relative* URLs when referencing + CSS files, scripts, and images. Add the base element just after the `` tag. If the `app` folder is the application root, as it is for this application, @@ -421,17 +427,17 @@ a#route-config `path`, the URL path segment for this route, and a `component`, the component associated with this route. - The router draws upon its registry of such route definitions when the browser URL changes + The router draws upon its registry of definitions when the browser URL changes or when application code tells the router to navigate along a route path. - In simpler terms, you might say of the first route: + In simpler terms, you might say this of the first route: - - When the browser's location URL changes to match the path segment `/crisis-center`, create or retrieve an instance of - the `CrisisListComponent` and display its view. + - When the browser's location URL changes to match the path segment `/crisis-center`, then + the router activates an instance of the `CrisisListComponent` and displays its view. - - When the application requests navigation to the path `/crisis-center`, create or retrieve an instance of - the `CrisisListComponent`, display its view, and update the browser's address location and history with the URL - for that path. + - When the application requests navigation to the path `/crisis-center`, the router + activates an instance of `CrisisListComponent`, displays its view, and updates the + browser's address location and history with the URL for that path. :marked Here is the first configuration. Pass the array of routes to the `RouterModule.forRoot` method. @@ -454,8 +460,8 @@ a#shell :marked ### The *AppComponent* shell - The root `AppComponent` is the application shell. It has a title at the top, a navigation bar with two links, - and a *router outlet* at the bottom where the router swaps views on and off the page. Here's what you get: + The root `AppComponent` is the application shell. It has a title, a navigation bar with two links, + and a *router outlet* where the router swaps views on and off the page. Here's what you get: figure.image-display img(src='/resources/images/devguide/router/shell-and-outlet.png' alt="Shell" width="300" ) @@ -475,9 +481,9 @@ a#router-outlet .l-sub-section :marked - It renders in the the DOM as a `` element. - The router inserts the outlet's view components as sibling elements, - immediately _after_ the closing `` tag. + The router adds the `` element to the DOM + and subsequently inserts the navigated view element + immediately _after_ the ``. a#router-link :marked @@ -517,7 +523,7 @@ a#router-link-active a#router-directives :marked - ### *Router Directives* + ### *Router directives* `RouterLink`, `RouterLinkActive` and `RouterOutlet` are directives provided by the Angular `RouterModule` package. They are readily available for you to use in the template. @@ -574,17 +580,17 @@ code-example. :marked That doesn't match any of the configured routes which means that the 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. + The user must click one of the links to trigger a navigation and display a component. It would be nicer if the application had a **default route** that displayed the list of heroes immediately, - just as it will when the user clicks the "Heroes" link or pastes `localhost:3000/heroes/` into the address bar. + just as it will when the user clicks the "Heroes" link or pastes `localhost:3000/heroes` into the address bar. a#redirect :marked ### Redirecting routes - The preferred solution is to add a `redirect` route that translates from the initial relative URL (`''`) - to the desired default path (`/heroes`). The browser address bar shows `~/heroes` as if you'd navigated there directly. + The preferred solution is to add a `redirect` route that translates the initial relative URL (`''`) + to the desired default path (`/heroes`). The browser address bar shows `.../heroes` as if you'd navigated there directly. Add the default route somewhere _above_ the wildcard route. It's just above the wildcard route in the following excerpt showing the complete `appRoutes` for this milestone. @@ -648,13 +654,13 @@ a#redirect .file router-sample .children .file app - .children - .file app.component.ts - .file app.module.ts - .file crisis-list.component.ts - .file hero-list.component.ts - .file not-found.component.ts - .file main.ts + .children + .file app.component.ts + .file app.module.ts + .file crisis-list.component.ts + .file hero-list.component.ts + .file not-found.component.ts + .file main.ts .file node_modules ... .file index.html .file package.json @@ -682,7 +688,7 @@ a#redirect .l-main-section#routing-module :marked - ## Milestone #2: The *Routing Module* + ## Milestone 2: *Routing module* In the initial route configuration, you provided a simple setup with two routes used to configure the application for routing. This is perfectly fine for simple routing. @@ -701,8 +707,6 @@ a#redirect Create a file named `app-routing.module.ts` in the `/app` folder to contain the routing module. - -:marked Import the `CrisisListComponent` and the `HeroListComponent` components just like you did in the `app.module.ts`. Then move the `Router` imports and routing configuration, including `RouterModule.forRoot`, into this routing module. @@ -721,6 +725,10 @@ a#redirect first importing the new-created `AppRoutingModule`from `app-routing.module.ts`, then replacing `RouterModule.forRoot` in the `imports` array with the `AppRoutingModule`. +makeExample('app/app.module.2.ts') +.l-sub-section + :marked + Later in this guide you will create [multiple routing modules](#hero-routing-module) and discover that + you must import those routing modules [in the correct order](#routing-module-order). :marked The application continues to work just the same, and you can use `AppRoutingModule` as @@ -750,59 +758,59 @@ a#why-routing-module .l-main-section#heroes-feature :marked - ## Milestone #3: The Heroes Feature + ## Milestone 3: Heroes feature - You've seen how to navigate using the `RouterLink` directive. + You've seen how to navigate using the `RouterLink` directive, + now you'll learn how to - Now you'll learn some new tricks such as how to - * organize the app and routes into *feature areas* using modules - * navigate imperatively from one component to another - * pass required and optional information in route parameters + * Organize the app and routes into *feature areas* using modules + * Navigate imperatively from one component to another + * Pass required and optional information in route parameters - To demonstrate, you'll build out the *Heroes* feature. + This example recreates the heroes feature in the "Services" episode of the + [Tour of Heroes tutorial](../tutorial/toh-pt4.html "Tour of Heroes: Services"), + and you'll be copying much of the code + from the . - ### The Heroes "feature area" + Here's how the user will experience this version of the app: - A typical application has multiple *feature areas*, each an island of functionality - with its own workflow(s), dedicated to a particular business purpose. - - You could continue to add files to the `app/` folder. - That's unrealistic and ultimately not maintainable. - Most developers prefer to put each feature area in its own folder. - - The first step is to **create a separate `app/heroes/` folder** - and add *Hero Management* feature files there. - - This example is pretty much a copy of the code and capabilities in the "[Tutorial: Tour of Heroes](../tutorial/index.html)". - There's no need to be more creative. - - 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 + A typical application has multiple *feature areas*, + each dedicated to a particular business purpose. + + While you could continue to add files to the `app/` folder, + that is unrealistic and ultimately not maintainable. + Most developers prefer to put each feature area in its own folder. + + You are about to break up the app into different *feature modules*, each with its own concerns. + Then you'll import into the main module and navigate among them. + :marked ### Add Heroes functionality - You are about to break up the app into different *feature modules*, each focused on its own concerns. - Then you'll import into the main module and navigate among them. + Follow these steps: + + - Create the `app/heroes` folder; you'll be adding files implementing *hero management* there. + - Delete the placeholder `hero-list.component.ts` that's in the `app` folder. + - Create a new `hero-list.component.ts` under `app/heroes`. + - Copy into it the contents of the `app.component.ts` from + the "Services" tutorial. + - Make a few minor but necessary changes: + - Delete the `selector` (routed component don't need them). + - Delete the `

`. + - Relabel the `

` to `

HEROES

`. + - Delete the `` at the bottom of the template. + - Rename the `AppComponent` class to `HeroListComponent`. + - Copy the `hero-detail.component.ts` and the `hero.service.ts` files into the `heroes` subfolder. + - Create a (pre-routing) `heroes.module.ts` in the heroes folder that looks like this: - First, create a `heroes.module.ts` in the heroes folder. - - Delete the placeholder `hero-list.component.ts` that's in the `app/` folder. - - Create a new `hero-list.component.ts` in the `app/heroes/` - folder and copy into it the contents of the final `heroes.component.ts` from the tutorial. - - Copy the `hero-detail.component.ts` and the `hero.service.ts` files into the `heroes/` folder. - - Add the `HeroService` to the `providers` array of the `Heroes` module - so its available to all components within the module. - - The `Heroes` module is ready for routing. - -+makeExcerpt('app/heroes/heroes.module.1.ts') ++makeExample('app/heroes/heroes.module.ts', 'v1','app/heroes/heroes.module.ts (pre-routing)') :marked - When you're done organizing, you have four *Hero Management* files: + When you're done, you'll have these *hero management* files: .filetree .file app/heroes @@ -813,27 +821,26 @@ figure.image-display .file heroes.module.ts :marked - Now it's time for some surgery to bring these files and the rest of the app - into alignment with the application router. - ### *Hero* feature routing requirements - The new Heroes feature has two interacting components, the list and the detail. + The heroes feature has two interacting components, the hero list and the hero detail. The list view is self-sufficient; you 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 to show on its own. That information must come from outside. - In this example, when the user selects a hero from the list, you navigate to the detail view to show that hero. + When the user selects a hero from the list, the app should navigate to the detail view + and show that hero. You tell the detail view which hero to display by including the selected hero's id in the route URL. +a#hero-routing-module +:marked ### *Hero* feature route configuration Create a new `heroes-routing.module.ts` in the `heroes` folder using the same techniques you learned while creating the `AppRoutingModule`. -+makeExcerpt('app/heroes/heroes-routing.module.ts') ++makeExample('app/heroes/heroes-routing.module.ts','','app/heroes/heroes-routing.module.ts') .l-sub-section :marked @@ -862,46 +869,127 @@ figure.image-display In any other module, you must call the `RouterModule.`**`forChild`** method to register additional routes. :marked - Import the `HeroRoutingModule` token from `heroes-routing.module.ts` into the `HeroesModule`, - just as you imported `AppRoutingModule` into the `AppModule`. + ### Add the routing module to the _HeroesModule_ + Add the `HeroRoutingModule` to the `HeroModule` + just as you added `AppRoutingModule` to the `AppModule`. -+makeExcerpt('app/heroes/heroes.module.ts (heroes routing)', 'heroes-routes') + Open `heroes.module.ts`. + Import the `HeroRoutingModule` token from `heroes-routing.module.ts` and + add it to the `imports` array of the `HeroesModule`. + The finished `HeroesModule` looks like this: + ++makeExample('app/heroes/heroes.module.ts','','app/heroes/heroes.module.ts') + +:marked + ### Remove duplicate hero routes + + The hero routes are currently defined in _two_ places: in the `HeroesRoutingModule`, + by way of the `HeroesModule, and in the `AppRoutingModule`. + + Routes provided by feature modules are combined together into their imported module's routes by the router. + This allows you to continue defining the feature module routes without modifying the main route configuration. + + But you don't want to define the same routes twice. + Remove the `HeroListComponent` import and the /heroes` route from the `app-routing.module.ts`. + + **Leave the default and the wildcard routes!** + These are concerns at the top level of the application itself. + ++makeExcerpt('app/app-routing.module.2.ts (v2)', '') + +a#merge-hero-routes +:marked + ### Import hero module into AppModule + The heroes feature module is ready, but the application doesn't know about the `HeroesModule` yet. + Open `app.module.ts` and revise it as follows. + + Import the `HeroesModule` and add it to the `imports` array in the `@NgModule` metadata of the `AppModule` + + Remove the `HeroListComponent` from the `AppModule`'s `declarations` because it's now provided by the `HeroesModule`. + This is important. There can be only _one_ owner for a declared component. + In this case, the `Heroes` module is the owner of the `Heroes` components and is making them available to + components in the `AppModule` via the `HeroesModule`. + + As a result, the `AppModule` no longer has specific knowledge of the hero feature, its components, or its route details. + You can evolve the hero feature with more components and different routes. + That's a key benefit of creating a separate module for each feature area. + + After these steps, the `AppModule` should look like this: + ++makeExample('app/app.module.3.ts','','app/app.module.ts') + +a#routing-module-order +:marked + ### Module import order matters + + Look at the module `imports` array. Notice that the `AppRoutingModule` is _last_. + Most importantly, it comes _after_ the `HeroesModule`. ++makeExample('app/app.module.3.ts','module-imports','app/app.module.ts (module-imports)')(format='.') + +:marked + The order of route configuration matters. + The router accepts the first route that matches a navigation request path. + + When all routes were in one `AppRoutingModule`, + you put the default and [wildcard](#wildcard-route) routes last, after the `/heroes` route, + so that the router had a chance to match a URL to the `/heroes` route _before_ + hitting the wildcard route and navigating to "Page not found". + + The routes are no longer in one file. + They are distributed across two modules, `AppRoutingModule` and `HeroesRoutingModule`. + + Each routing module augments the route configuration _in the order of import_. + If you list `AppRoutingModule` first, the wildcard route will be registered + _before_ the hero routes. + The wildcard route — which matches _every_ URL — + will intercept the attempt to navigate to a hero route. + +.l-sub-section + :marked + Reverse the routing modules and see for yourself that + a click of the heroes link results in "Page not found". + Learn about inspecting the runtime router configuration + [below](#inspect-config "Inspect the router config"). :marked ### Route definition with a parameter + + Return to the `HeroesRoutingModule` and look at the route definitions again. The route to `HeroDetailComponent` has a twist. +makeExcerpt('app/heroes/heroes-routing.module.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, you're expecting the router to insert the `id` of a hero into that slot. + In this case, the router will insert the `id` of a hero into that slot. - If you tell the router to navigate to the detail component and display "Magneta", you expect hero `id` (15) to appear in the - browser URL like this: -code-example(format="." language="bash"). + If you tell the router to navigate to the detail component and display "Magneta", + you expect a hero id to appear in the browser URL like this: + +code-example(format="nocode"). localhost:3000/hero/15 + :marked If a user enters that URL into the browser address bar, the router should recognize the pattern and go to the same "Magneta" detail view. -.l-sub-section + +.callout.is-helpful + header Route parameter: Required or optional? :marked - #### Route parameter: Required or optional? - Embedding the route parameter token, `:id`, in the route definition path is a good choice for this scenario + Embedding the route parameter token, `:id`, + in the route definition path is a good choice for this scenario because the `id` is *required* by the `HeroDetailComponent` and because the value `15` in the path clearly distinguishes the route to "Magneta" from a route for some other hero. - An [optional-route-parameter](#optional-route-parameters) might be a better choice if you were passing an *optional* value to `HeroDetailComponent`. - a#navigate :marked ### Navigate to hero detail imperatively - *Users won't navigate to the detail component by clicking a link* - so you won't be adding a new `RouterLink` anchor tag to the shell. + Users *will not* navigate to the detail component by clicking a link + so you won't add a new `RouterLink` anchor tag to the shell. - Instead, when the user *clicks* a hero in the list, you'll *command* the router + Instead, when the user *clicks* a hero in the list, you'll ask the router to navigate to the hero detail view for the selected hero. Start in the `HeroListComponent`. @@ -916,8 +1004,8 @@ a#navigate :marked The template defines an `*ngFor` repeater such as [you've seen before](displaying-data.html#ngFor). - There's a `(click)` [EventBinding](template-syntax.html#event-binding) to the component's `onSelect` method - which you implement as follows: + There's a `(click)` [event binding](template-syntax.html#event-binding) to the component's + `onSelect` method which you implement as follows: +makeExcerpt('app/heroes/hero-list.component.1.ts', 'select') @@ -925,8 +1013,10 @@ a#navigate The component's `onSelect` calls the router's **`navigate`** method with a _link parameters array_. You can use this same syntax in a `RouterLink` if you decide later to navigate in HTML template rather than in component code. -h3#route-parameters Setting the route parameters in the list view +a#route-parameters :marked + ### Setting the route parameters in the list view + After navigating to the `HeroDetailComponent`, you expect to see the details of the selected hero. You'll need *two* pieces of information: the routing path to the component and the hero's `id`. @@ -936,33 +1026,30 @@ h3#route-parameters Setting the route parameters in the list view +makeExcerpt('app/heroes/hero-list.component.1.ts', 'link-parameters-array') :marked - The router composes the following two-part URL from this array: - -code-example(language="bash"). - localhost:3000/hero/15 + The router composes the destination URL from this array: + `localhost:3000/hero/15`. 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. + Don't analyze the URL. Let the router do it. The router extracts the route parameter (`id:15`) from the URL and supplies it to the `HeroDetailComponent` via the `ActivatedRoute` service. - -h3#activated-route ActivatedRoute: the one-stop-shop for route information +a#activated-route :marked - Each route contains information about its path, data parameters, URL segment and much more. - All of this information is available in an injected service provided by the router called the [ActivatedRoute](../api/router/index/ActivatedRoute-interface.html). + ### ActivatedRoute: the one-stop-shop for route information - The `ActivatedRoute` contains all the information you need from the current route component as well as ways to get information - about other activated routes in the `RouterState`. + The route path and parameters are available through an injected router service called the + [ActivatedRoute](../api/router/index/ActivatedRoute-interface.html). + It has a great deal of useful information including: .l-sub-section :marked - **`url`**: An `Observable` of the route path(s). The value is provided as an array of strings for each part of the route path. + **`url`**: An `Observable` of the route path(s), represented as an array of strings for each part of the route path. **`data`**: An `Observable` that contains the `data` object provided for the route. Also contains any resolved values from the [resolve guard](#resolve-guard). @@ -1098,21 +1185,25 @@ a#nav-to-list .l-main-section#optional-route-parameters :marked - ### Route Parameters + ### Route Parameters: Required or optional? Use [*route parameters*](#route-parameters) to specify a *required* parameter value *within* the route URL - as you do when navigating to the `HeroDetailComponent` in order to view-and-edit the hero with *id:15*. -code-example(format="." language="bash"). + as you do when navigating to the `HeroDetailComponent` in order to view the hero with *id* 15: + +code-example(format="nocode"). localhost:3000/hero/15 + :marked - Sometimes you wish to add *optional* information to a route request. - For example, the `HeroListComponent` doesn't need help to display a list of heroes. - But it might be nice if the previously-viewed hero were pre-selected when returning from the `HeroDetailComponent`. + You can also add *optional* information to a route request. + For example, when returning to the heroes list from the hero detail view, + it would be nice if the viewed hero was preselected in the list. + figure.image-display img(src='/resources/images/devguide/router/selected-hero.png' alt="Selected hero") + :marked - That becomes possible if you can include hero Magneta's `id` in the URL when you - return from the `HeroDetailComponent`, a scenario you'll pursue in a moment. + You'll implement this feature in a moment by including the viewed hero's `id` + in the URL as an optional parameter when returning from the `HeroDetailComponent`. Optional information takes other forms. Search criteria are often loosely structured, e.g., `name='wind*'`. Multiple values are common — `after='12/31/2015' & before='1/1/2017'` — in no particular order — @@ -1122,24 +1213,17 @@ figure.image-display doing so greatly complicates the pattern matching required to translate an incoming URL to a named route. Optional parameters are the ideal vehicle for conveying arbitrarily complex information during navigation. - Optional parameters aren't involved in pattern matching and afford enormous flexibility of expression. + Optional parameters aren't involved in pattern matching and afford flexibility of expression. - The Router supports navigation with optional parameters as well as required route parameters. + The router supports navigation with optional parameters as well as required route parameters. Define _optional_ parameters in a separate object _after_ you define the required route parameters. - ### Route Parameters: Required or Optional? + In general, prefer a *required route parameter* when + the value is mandatory (for example, if necessary to distinguish one route path from another); + prefer an *optional parameter* when the value is optional, complex, and/or multi-variate. - There is no hard-and-fast rule. In general, - - *prefer a required route parameter when* - * the value is required. - * the value is necessary to distinguish one route path from another. - - *prefer an optional parameter when* - * the value is optional, complex, and/or multi-variate. - - - ### Route parameter +:marked + ### Heroes list: optionally selecting a hero When navigating to the `HeroDetailComponent` you specified the _required_ `id` of the hero-to-edit in the *route parameter* and made it the second item of the [_link parameters array_](#link-parameters-array). @@ -1313,47 +1397,11 @@ h3#route-animation Adding animations to the routed component Applying route animations to individual components is something you'd rather not do throughout the entire application. It would be better to animate routes based on _route paths_, a topic to cover in a future update to this guide. -h3#merge-hero-routes Import hero module into AppModule :marked - The heroes feature module is ready, but the application doesn't know about the `HeroesModule` yet. - Open `app.module.ts` and revise it as follows. - - Import the `HeroesModule` and add it to the `imports` array in the `@NgModule` metadata of the `AppModule` - - Remove the `HeroListComponent` from the `AppModule`'s `declarations` because it's now provided by the `HeroesModule`. - This is important. There can be only _one_ owner for a declared component. - In this case, the `Heroes` module is the owner of the `Heroes` components and is making them available to - components in the `AppModule` via the `HeroesModule`. - - After these steps, the `AppModule` should look like this: - -+makeExcerpt('app/app.module.3.ts') - -:marked - -.l-sub-section - :marked - Routes provided by feature modules are combined together into their imported module's routes by the router. - This allows you to continue defining the feature module routes without modifying the main route configuration. - -:marked - As a result, the `AppModule` no longer has specific knowledge of the hero feature, its components, or its route details. - You can evolve the hero feature with more components and different routes. - That's a key benefit of creating a separate module for each feature area. - - Since the `Heroes` routes are defined within the feature module, you can also remove the initial `heroes` route from the `app-routing.module.ts`. - - But leave the default and the wildcard routes! - These are concerns at the top level of the application itself. - -+makeExcerpt('app/app-routing.module.2.ts (v2)', '') - -:marked - ### Heroes App Wrap-up - - You've reached the second milestone in your router education. + ### Milestone 3 wrap-up You've learned how to + * organize the app into *feature areas* * navigate imperatively from one component to another * pass information along in route parameters and subscribe to them in the component @@ -1361,6 +1409,7 @@ h3#merge-hero-routes Import hero module into AppModule * apply animations to the route component After these changes, the folder structure looks like this: + .filetree .file router-sample .children @@ -1383,9 +1432,7 @@ h3#merge-hero-routes Import hero module into AppModule .file package.json .file styles.css .file tsconfig.json -:marked - - ### The Heroes App code + Here are the relevant files for this version of the sample application. +makeTabs( @@ -1407,88 +1454,79 @@ h3#merge-hero-routes Import hero module into AppModule heroes.module.ts, heroes-routing.module.ts`) +a#milestone-4 .l-main-section#crisis-center-feature :marked - ## Milestone #4: The Crisis Center + ## Milestone 4: Crisis center feature - The *Crisis Center* is a fake view at the moment. Time to make it useful. + It's time to add real features to the app's current placeholder crisis center. + + Begin by imitating the heroes feature: + + - Delete the placeholder crisis center file. + - Create !{_an} `!{_appDir}/crisis-center` folder. + - Copy the files from `!{_appDir}/heroes` into the new crisis center folder, but + - Change every mention of "hero" to "crisis", and "heroes" to "crises". + + You'll turn the `CrisisService` into a purveyor of mock crises instead of mock heroes: - The new *Crisis Center* begins as a virtual copy of the *Heroes* module. - 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Ă , another feature module! - - There's no point to this exercise unless you can learn something. - This section introduces new ideas and techniques into the _Crisis Center_ design: - - * The route URLs will branch into child route trees that parallel the component trees in the feature areas. - - * The router will prevent navigation away from the detail view while there are pending, unsaved changes. - - * The user will be able to cancel unwanted changes. - - * The router will block access to certain features until the user logs-in. - - * In keeping with [*Separation of Concerns*](https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html) - principle, changes to a feature module such as *Crisis Center* won't require changes to the `AppModule` or - any other feature's component. - - Leave *Heroes* in its current state as a contrast with the *Crisis Center*. - You can decide later if the differences are worthwhile. ++makeExcerpt('app/crisis-center/crisis.service.ts', 'mock-crises') :marked - ### A Crisis Center with child routes + The resulting crisis center is a foundation for introducing a new concept — **child routing**. + You can leave *Heroes* in its current state as a contrast with the *Crisis Center* + and decide later if the differences are worthwhile. - You'll organize the *Crisis Center* to conform to the following recommended pattern for Angular applications. - * each feature area in its own folder within a defined module - * each area with its own area root component - * each area root component with its own router-outlet and child routes - * area routes rarely (if ever) cross +.alert.is-helpful + :marked + In keeping with the + *Separation of Concerns* principle, + changes to the *Crisis Center* won't affect the `!{_AppModuleVsAppComp}` or + any other feature's component. - If you had many feature areas, their component trees might look like this: +:marked + ### A Crisis center with child routes + + You'll organize the crisis center to conform to the following recommended pattern for Angular applications: + + * Each feature area resides in its own folder + * Each feature has its own Angular feature module. + * Each area has its own area root component. + * Each area root component has its own router outlet and child routes. + * Feature area routes rarely (if ever) cross with routes of other features. + + If your app had many feature areas, the app component trees might look like this: figure.image-display img(src='/resources/images/devguide/router/component-tree.png' alt="Component Tree" ) -a#child-routing-component :marked - ### Child Routing Component + ### Child routing component Add the following `crisis-center.component.ts` to the `crisis-center` folder: +makeExcerpt('app/crisis-center/crisis-center.component.ts (minus imports)', 'minus-imports') :marked - The `CrisisCenterComponent` is much like the `AppComponent` shell. + Much like the `AppComponent`, the `CrisisCenterComponent` is the + + - *Root* of the crisis center area, + just as `AppComponent` is the root of the entire application + - *Shell* for the crisis management feature area, + just as the `AppComponent` is a shell to manage the high-level workflow - * It is the root of the *Crisis Center* area - just as `AppComponent` is the root of the entire application. + Like most shells, the `CrisisCenterComponent` class is very simple, simpler even than `AppComponent`: + it has no business logic, and its template has no links, just a title and + `` for the crisis center child views. - * It is a shell for the crisis management feature area - just as the `AppComponent` is a shell to manage the high-level workflow. - - * It is dead simple — simpler even than the `AppComponent` template. - It has no content, no links, just a `` for the *Crisis Center* child views. - - Unlike `AppComponent` (and most other components), it _lacks a selector_. - It doesn't need one. You don't *embed* this component in a parent template. - You *navigate* to it from the outside, via the router. - -.l-sub-section - :marked - You *can* give it a selector. There's no harm in it. - The point is that you don't *need* one because you only *navigate* to it. + Unlike `AppComponent`, and most other components, it _lacks a selector_. + It doesn't _need_ one since you don't *embed* this component in a parent template, + instead you use the router to *navigate* to it. :marked - ### Child Route Configuration + ### Child route configuration - The `CrisisCenterComponent` is a *Routing Component* like the `AppComponent`. + The `CrisisCenterComponent` is a *routing component* like the `AppComponent`. It has its own `RouterOutlet` and its own child routes. Add the following `crisis-center-home.component.ts` to the `crisis-center` folder. @@ -1506,10 +1544,10 @@ a#child-routing-component with a single route containing the `CrisisListComponent`. The `CrisisListComponent` route also has a `children` array with two routes. - These two routes navigate to the two *Crisis Center* child components, - `CrisisCenterHomeComponent` and `CrisisDetailComponent`. + These two routes navigate to the crisis center child components, + `CrisisCenterHomeComponent` and `CrisisDetailComponent`, respectively. - There are some *important differences* in the treatment of these routes. + There are *important differences* in the way the router treats these _child routes_. The router displays the components of these routes in the `RouterOutlet` of the `CrisisCenterComponent`, not in the `RouterOutlet` of the `AppComponent` shell. @@ -1519,23 +1557,21 @@ a#child-routing-component The `Crisis Detail` route is a child of the `Crisis List`. Since the router [reuses components](#reuse) by default, the `Crisis Detail` component will be re-used as you select different crises. - In contrast, back in the `Hero Detail` route, the component was recreated each time you selected a different hero. At the top level, paths that begin with `/` refer to the root of the application. - But these are child routes. - They *extend* the path of the parent route. - With each step down the route tree, you add a slash followed by the route path (unless the route path is _empty_). + But child routes *extend* the path of the parent route. + With each step down the route tree, + you add a slash followed by the route path, unless the path is _empty_. - For example, the parent path to the `CrisisCenterComponent` is `/crisis-center` - The router appends these child paths to the parent path to the `CrisisCenterComponent` (`/crisis-center`). + Apply that logic to navigation within the crisis center for which the parent path is `/crisis-center`. - * to navigate to the `CrisisCenterHomeComponent`, the full URL is `/crisis-center` (`/crisis-center` + `''` + `''`). + * To navigate to the `CrisisCenterHomeComponent`, the full URL is `/crisis-center` (`/crisis-center` + `''` + `''`). - * to navigate to the `CrisisDetailComponent` for a crisis with `id=2`, the full URL is - `/crisis-center/2` (`/crisis-center` + `''` + `'/2'`). + * To navigate to the `CrisisDetailComponent` for a crisis with `id=2`, the full URL is + `/crisis-center/2` (`/crisis-center` + `''` + `'/2'`). - The absolute URL for the latter example, including the origin, is + The absolute URL for the latter example, including the `localhost` origin, is code-example. localhost:3000/crisis-center/2 @@ -1544,9 +1580,12 @@ code-example. +makeExcerpt('app/crisis-center/crisis-center-routing.module.1.ts', '') -h3#import-crisis-module Import crisis center module into the AppModule routes +a#import-crisis-module :marked - As with the `Heroes` module, you must import the `Crisis Center` module into the `AppModule`: + ### Import crisis center module into the *AppModule* routes + + As with the `HeroesModule`, you must add the `CrisisCenterModule` to the `imports` array of the `AppModule` + _before_ the `AppRoutingModule`: +makeExcerpt('app/app.module.4.ts (import CrisisCenterModule)', 'crisis-center-module') @@ -1556,13 +1595,15 @@ h3#import-crisis-module Import crisis center module into the AppModule routes The `app-routing.module.ts` file retains the top-level application routes such as the default and wildcard routes. -+makeExcerpt('app/app-routing.module.3.ts (v3)', 'v3') ++makeExcerpt('app/app-routing.module.3.ts (v3)') .l-main-section -h2#relative-navigation Relative Navigation +a#relative-navigation :marked - While building out the *Crisis Center* feature, you navigated to the - *Crisis Detail* route using a so-called **absolute path** that begins with a _slash_. + ### Relative navigation + + While building out the crisis center feature, you navigated to the + crisis detail route using an **absolute path** that begins with a _slash_. The router matches such _absolute_ paths to routes starting from the top of the route configuration. @@ -1577,14 +1618,14 @@ h2#relative-navigation Relative Navigation .l-sub-section :marked - The _link parameters array_ supports a directory-like syntax for relative navigation. + The router supports directory-like syntax in a _link parameters list_ to help guide route name lookup: `./` or `no leading slash` is relative to the current level. `../` to go up one level in the route path. - The can combine relative navigation syntax with an ancestor path. - If you must navigate to a sibling route, you could use the `../` convention to go up + You can combine relative navigation syntax with an ancestor path. + If you must navigate to a sibling route, you could use the `../` convention to go up one level, then over and down the sibling route path. :marked @@ -1599,7 +1640,7 @@ h2#relative-navigation Relative Navigation **Always** specify the complete _absolute_ path when calling router's `navigateByUrl` method. :marked - ### Navigate to Crisis Detail with a relative URL + ### Navigate to crisis detail with a relative URL Update the *Crisis List* `onSelect` method to use relative navigation so you don't have to start from the top of the route configuration. @@ -1628,35 +1669,42 @@ h2#relative-navigation Relative Navigation Notice that the path goes up a level (`../`) syntax. If the current crisis `id` is `1`, the resulting path back to the crisis list is `/crisis-center/;id=3;foo=foo`. +a#named-outlets .l-main-section -h3#named-outlets Displaying Multiple Routes in Named Outlets :marked - In this application, you decide to give users a way to contact the `Crisis Center`. - When a user clicks a "Contact" button, you want to display a message textbox in a popup view. + ### Displaying multiple routes in named outlets + + You decide to give users a way to contact the crisis center. + When a user clicks a "Contact" button, you want to display a message in a popup view. The popup should stay open, even when switching between pages in the application, until the user closes it by sending the message or canceling. Clearly you can't put the popup in the same outlet as the other pages. - Until now, you've defined a single outlet and you've nested child routes under that outlet to group routes together. - In fact, the `Router` only supports one primary `unnamed` outlet per template. + Until now, you've defined a single outlet and you've nested child routes + under that outlet to group routes together. + The router only supports one primary _unnamed_ outlet per template, - As it happens, a template can also have _any_ number of _named outlets_. + A template can also have any number of _named_ outlets. Each named outlet has its own set of routes with their own components. Multiple outlets can be displaying different content, determined by different routes, all at the same time. - Add an outlet named "popup" in the `AppComponent`, directly below the regular _unnamed_ outlet. + Add an outlet named "popup" in the `AppComponent`, directly below the unnamed outlet. + +makeExcerpt('app/app.component.4.ts', 'outlets') + :marked That's where a popup will go, once you learn how to route a popup component to it. a#secondary-routes :marked - #### Secondary routes + #### Secondary routes + Named outlets are the targets of _secondary routes_. Secondary routes look like primary routes and you configure them the same way. They differ in a few key respects. + * They are independent of each other * They work in combination with other routes. * They are displayed in named outlets. @@ -1667,12 +1715,17 @@ a#secondary-routes figure.image-display img(src='/resources/images/devguide/router/contact-popup.png' alt="Contact popup" width="250") + :marked Here's the component and its template: + +makeTabs( - `router/ts/app/compose-message.component.ts, router/ts/app/compose-message.component.html`, + `router/ts/app/compose-message.component.ts, + router/ts/app/compose-message.component.html`, null, - `app/compose-message.component.ts, app/compose-message.component.html`) + `app/compose-message.component.ts, + app/compose-message.component.html`) + :marked It looks about the same as any other component you've seen in this guide. There are two noteworthy differences @@ -1680,7 +1733,7 @@ figure.image-display Note that the `send` method simulates latency by waiting a second before "sending" the message and closing the popup. The `closePopup` method closes the popup view by navigating to the "popup" outlet with a `null`. - That's a peculiarity covered [below](#clear-secondary-routes) + That's a peculiarity covered [below](#clear-secondary-routes). As with other application components, you add the `ComposeMessageComponent` to the `declarations` of an `NgModule`. Do so in the `AppModule`. @@ -1728,10 +1781,10 @@ h3#secondary-route-navigation Secondary Route Navigation: merging routes you should see something like the following URL in the browser address bar. code-example. - http:////crisis-center(popup:compose) + http://.../crisis-center(popup:compose) :marked - The interesting part of the URL follows the ``: + The interesting part of the URL follows the `...`: * The `crisis-center` is the primary navigation. * Parentheses surround the secondary route. * The secondary route consist of an outlet name (`popup`), then a `colon` separator, followed with the secondary route path (`compose`) @@ -1739,7 +1792,7 @@ code-example. Click the _Heroes_ link and look at the URL again. code-example. - http:////heroes(popup:compose) + http://.../heroes(popup:compose) :marked The primary navigation part has changed; the secondary route is the same. @@ -1756,7 +1809,7 @@ code-example. a#clear-secondary-routes :marked - #### Clearing Secondary Routes + #### Clearing secondary routes As you've learned, a component in an outlet persists until you navigate away to a new component. Secondary outlets are no different in this regard. @@ -1780,11 +1833,11 @@ a#clear-secondary-routes .l-main-section#guards :marked - ## Milestone #5: Route Guards + ## Milestone 5: Route guards At the moment, *any* user can navigate *anywhere* in the application *anytime*. - That's not always the right thing to do. + * Perhaps the user is not authorized to navigate to the target component. * Maybe the user must login (*authenticate*) first. * Maybe you should fetch some data before you display the target component. @@ -1794,11 +1847,14 @@ a#clear-secondary-routes You can add _guards_ to the route configuration to handle these scenarios. A guard's return value controls the router's behavior: + * if it returns `true`, the navigation process continues * if it returns `false`, the navigation process stops and the user stays put + .l-sub-section :marked The guard can also tell the router to navigate elsewhere, effectively canceling the current navigation. + :marked The guard *might* return its boolean answer synchronously. But in many cases, the guard can't produce an answer synchronously. @@ -1842,7 +1898,7 @@ a#can-activate-guard #### Add an admin feature module - In this next section, you'll extend the Crisis Center with some new *administrative* features. + In this next section, you'll extend the crisis center with some new *administrative* features. Those features aren't defined yet. But you can start by adding a new feature module named `AdminModule`. @@ -1917,6 +1973,7 @@ h3#component-less-route Component-Less Route: grouping routes without a c :marked #### Guard the admin feature + Currently every route within the *Crisis Center* is open to everyone. The new *admin* feature should be accessible only to authenticated users. @@ -1942,6 +1999,7 @@ h3#component-less-route Component-Less Route: grouping routes without a c The admin feature is now protected by the guard, albeit protected poorly. #### Teach *AuthGuard* to authenticate + Make the `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. @@ -1974,6 +2032,7 @@ h3#component-less-route Component-Less Route: grouping routes without a c This secondary navigation automatically cancels the current navigation; you return `false` just to be clear about that. #### Add the *LoginComponent* + You need a `LoginComponent` for the user to log in to the app. After logging in, you'll redirect to the stored URL if available, or use the default URL. There is nothing new about this component or the way you wire it into the router configuration. @@ -2051,7 +2110,7 @@ h3#can-deactivate-guard CanDeactivate: handling unsaved changes You need the `CanDeactivate` guard. - ### Cancel and Save + ### Cancel and save The sample application doesn't talk to a server. Fortunately, you have another way to demonstrate an asynchronous router hook. @@ -2234,7 +2293,7 @@ h3#resolve-guard Resolve: pre-fetching component data a#query-parameters a#fragment :marked - ### Query Parameters and Fragments + ### Query parameters and fragments In the [route parameters](#optional-route-parameters) example, you only dealt with parameters specific to the route, but what if you wanted optional parameters available to all routes? @@ -2287,7 +2346,7 @@ include ../../../_includes/_see-addr-bar .l-main-section#asynchronous-routing :marked - ## Milestone #6: Asynchronous Routing + ## Milestone 6: Asynchronous routing As you have completed the milestones, the application has naturally gotten larger. As you continue to build out feature areas, the overall application size will get larger also. @@ -2375,8 +2434,9 @@ h3#can-load-guard CanLoad Guard: guarding unauthorized loading of feature +makeExample('router/ts/app/app-routing.module.5.ts', 'admin', 'app-routing.module.ts (lazy admin route)') -h3#preloading Preloading: background loading of feature areas +a#preloading :marked + ### _Preloading_: background loading of feature areas You've learned how to load modules on-demand. You can also load modules asynchronously with _preloading_. @@ -2400,6 +2460,7 @@ h3#preloading Preloading: background loading of feature areas That's _preloading_. #### How it works + After each _successful_ navigation, the router looks in its configuration for an unloaded module that it can preload. Whether it preloads a module and which modules it preloads depends upon the *preload strategy*. @@ -2414,7 +2475,8 @@ h3#preloading Preloading: background loading of feature areas In this next section, you'll update the `CrisisCenterModule` to load lazily by default and use the `PreloadAllModules` strategy to load it (and _all other_ lazy loaded modules) as soon as possible. - #### Lazy load the _Crisis Center_ + #### Lazy load the _crisis center_ + Update the route configuration to lazy load the `CrisisCenterModule`. Take the same steps you used to configure `AdminModule` for lazy load. @@ -2458,6 +2520,7 @@ h3#preloading Preloading: background loading of feature areas a#preload-canload :marked #### CanLoad blocks preload + The `PreloadAllModules` strategy does not load feature areas protected by a [CanLoad](#can-load-guard) guard. This is by design. @@ -2528,17 +2591,35 @@ a#custom-preloading Verify this by logging in to the `Admin` feature area and noting that the `crisis-center` is listed in the `Preloaded Modules`. It's also logged to the browser's console. - +a#inspect-config +.l-main-section +:marked + ## Inspect the router's configuration + + You put a lot of effort into configuring the router in several routing module files + and were careful to list them [in the proper order](#routing-module-order). + Are routes actually evaluated as you planned? + How is the router really configured? + + You can inspect the router's current configuration any time by injecting it and + examining its `config` property. + For example, update the `AppModule` as follows and look in the browser console window + to see the finished route configuration. ++makeExcerpt('app/app.module.ts (inspect the router config)', 'inspect-config') + +a#final-app .l-main-section :marked ## Wrap Up - We've covered a lot of ground in this guide and the application is too big to reprint here. + + You've covered a lot of ground in this guide and the application is too big to reprint here. Please visit the and where you can download the final source code. .l-main-section :marked ## Appendices + The balance of this guide is a set of appendices that elaborate some of the points you covered quickly above. @@ -2546,11 +2627,10 @@ a#custom-preloading .l-main-section#link-parameters-array :marked - ## Appendix: Link Parameters Array - - The _link parameters array_ has been mentioned several times and used in several places. + ## Appendix: Link parameters array A link parameters array holds the ingredients for router navigation: + * the *path* of the route to the destination component * required and optional route parameters that go into the route URL @@ -2570,14 +2650,15 @@ a#custom-preloading :marked These three examples cover the need for an app with one level routing. - The moment you add a child router, such as the *Crisis Center*, you create new link array possibilities. + The moment you add a child router, such as the crisis center, you create new link array possibilities. - Recall that you specified a default child route for *Crisis Center* so this simple `RouterLink` is fine. + Recall that you specified a default child route for crisis center so this simple `RouterLink` is fine. +makeExcerpt('app/app.component.3.ts', 'cc-anchor-w-default', '') :marked Parse it out. + * The first item in the array identifies the parent route ('/crisis-center'). * There are no parameters for this parent route so you're done with it. * There is no default for the child route so you need to pick one. @@ -2619,35 +2700,43 @@ a#browser-url-styles and should not reload the page. Modern HTML 5 browsers support - [history.pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries), + history.pushState, 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 can 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 Router supports both styles with two `LocationStrategy` providers: + The router supports both styles with two `LocationStrategy` providers: + 1. `PathLocationStrategy` - the default "HTML 5 pushState" style. 1. `HashLocationStrategy` - the "hash URL" style. The `RouterModule.forRoot` function sets the `LocationStrategy` to the `PathLocationStrategy`, making it the default strategy. You can switch to the `HashLocationStrategy` with an override during the bootstrapping process if you prefer it. + .l-sub-section :marked Learn about "providers" and the bootstrap process in the [Dependency Injection guide](dependency-injection.html#bootstrap) + :marked - ### Which Strategy is Best? + ### Which strategy is best? + You must choose a strategy and you 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. @@ -2669,12 +2758,14 @@ code-example(format=".", language="bash"). resort to hash routes. ### HTML 5 URLs and the *<base href>* - While the router uses the "[HTML 5 pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries)" + + While the router uses the + HTML 5 pushState style by default, you *must* configure that strategy with a **base href** 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`. + <base href> element + tag in the `` of the `index.html`. +makeExcerpt('index.html', 'base-href', '') @@ -2696,8 +2787,10 @@ code-example(format=".", language="bash"). :marked Learn about the [APP_BASE_HREF](../api/common/index/APP_BASE_HREF-let.html) in the API Guide. + :marked ### *HashLocationStrategy* + You can go old-school with the `HashLocationStrategy` by providing the `useHash: true` in an object as the second argument of the `RouterModule.forRoot` in the `AppModule`. diff --git a/public/resources/images/devguide/router/shell-and-outlet.png b/public/resources/images/devguide/router/shell-and-outlet.png index 50ddcf913834f5d70561f75684b260c3cdee3e3b..2eca7af3530ed9fdef85e41cf915892a29f22b02 100644 GIT binary patch literal 4683 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 N002ovPDHLkV1ki`ad!X! literal 11643 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