From 8e3e0834b55968f6f28a74f66567797c543aa17a Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Thu, 31 Dec 2015 16:55:53 -0800 Subject: [PATCH] docs(router): fixes recent issues closes #619 & #626 --- .../router/ts/app/app.component.1.ts | 6 +- .../router/ts/app/app.component.2.ts | 8 ++- .../router/ts/app/app.component.3.ts | 11 +++- .../_examples/router/ts/app/app.component.ts | 10 ++- public/docs/_examples/router/ts/app/boot.1.ts | 2 +- public/docs/_examples/router/ts/app/boot.2.ts | 25 +++---- public/docs/_examples/router/ts/app/boot.3.ts | 6 +- public/docs/_examples/router/ts/app/boot.ts | 8 +-- .../app/crisis-center/add-crisis.component.ts | 2 +- .../crisis-center/crisis-center.component.ts | 3 +- .../crisis-center/crisis-detail.component.ts | 25 +++---- .../crisis-center/crisis-list.component.ts | 2 +- .../ts/app/heroes/hero-detail.component.ts | 2 +- .../ts/app/heroes/hero-list.component.ts | 4 +- public/docs/ts/latest/guide/router.jade | 65 +++++++++++-------- 15 files changed, 96 insertions(+), 83 deletions(-) diff --git a/public/docs/_examples/router/ts/app/app.component.1.ts b/public/docs/_examples/router/ts/app/app.component.1.ts index 62f397bb07..3caad41fe8 100644 --- a/public/docs/_examples/router/ts/app/app.component.1.ts +++ b/public/docs/_examples/router/ts/app/app.component.1.ts @@ -13,8 +13,10 @@ import {HeroListComponent} from './hero-list.component'; // #docregion template template: `

Component Router

- Crisis Center - Heroes + `, // #enddocregion template diff --git a/public/docs/_examples/router/ts/app/app.component.2.ts b/public/docs/_examples/router/ts/app/app.component.2.ts index c5b4c03ac7..4ab0f7aaf2 100644 --- a/public/docs/_examples/router/ts/app/app.component.2.ts +++ b/public/docs/_examples/router/ts/app/app.component.2.ts @@ -9,16 +9,20 @@ import {CrisisListComponent} from './crisis-list.component'; // #docregion hero-import import {HeroListComponent} from './heroes/hero-list.component'; import {HeroDetailComponent} from './heroes/hero-detail.component'; +import {HeroService} from './heroes/hero.service'; // #enddocregion hero-import @Component({ selector: 'my-app', template: `

Component Router

- Crisis Center - Heroes + `, + providers: [HeroService], directives: [ROUTER_DIRECTIVES] }) // #enddocregion diff --git a/public/docs/_examples/router/ts/app/app.component.3.ts b/public/docs/_examples/router/ts/app/app.component.3.ts index d5e9325e47..f57a57f351 100644 --- a/public/docs/_examples/router/ts/app/app.component.3.ts +++ b/public/docs/_examples/router/ts/app/app.component.3.ts @@ -7,6 +7,8 @@ import {CrisisCenterComponent} from './crisis-center/crisis-center.component'; import {HeroListComponent} from './heroes/hero-list.component'; import {HeroDetailComponent} from './heroes/hero-detail.component'; +import {DialogService} from './dialog.service'; +import {HeroService} from './heroes/hero.service'; @Component({ selector: 'my-app', @@ -38,12 +40,15 @@ import {HeroDetailComponent} from './heroes/hero-detail.component'; // #docregion template template: `

Component Router

- Crisis Center - Princess Crisis - Dragon Crisis + `, // #enddocregion template + providers: [DialogService, HeroService], directives: [ROUTER_DIRECTIVES] }) @RouteConfig([ diff --git a/public/docs/_examples/router/ts/app/app.component.ts b/public/docs/_examples/router/ts/app/app.component.ts index 95bbdcafea..d2ca8c0c4c 100644 --- a/public/docs/_examples/router/ts/app/app.component.ts +++ b/public/docs/_examples/router/ts/app/app.component.ts @@ -7,16 +7,22 @@ import {CrisisCenterComponent} from './crisis-center/crisis-center.component'; import {HeroListComponent} from './heroes/hero-list.component'; import {HeroDetailComponent} from './heroes/hero-detail.component'; +import {DialogService} from './dialog.service'; +import {HeroService} from './heroes/hero.service'; + @Component({ selector: 'my-app', // #docregion template template: `

Component Router

- Crisis Center - Heroes + `, // #enddocregion template + providers: [DialogService, HeroService], directives: [ROUTER_DIRECTIVES] }) // #docregion route-config diff --git a/public/docs/_examples/router/ts/app/boot.1.ts b/public/docs/_examples/router/ts/app/boot.1.ts index 6b8d17bd4d..8dfbeb39eb 100644 --- a/public/docs/_examples/router/ts/app/boot.1.ts +++ b/public/docs/_examples/router/ts/app/boot.1.ts @@ -18,6 +18,6 @@ bootstrap(AppComponent, [ import {AppComponent as ac} from './app.component.1'; bootstrap(ac, [ // #docregion all - ROUTER_PROVIDERS, + ROUTER_PROVIDERS ]); // #enddocregion all \ No newline at end of file diff --git a/public/docs/_examples/router/ts/app/boot.2.ts b/public/docs/_examples/router/ts/app/boot.2.ts index e020baa014..1451041616 100644 --- a/public/docs/_examples/router/ts/app/boot.2.ts +++ b/public/docs/_examples/router/ts/app/boot.2.ts @@ -4,16 +4,11 @@ // #docplaster // #docregion v2 -// #docregion hash-strategy import {bootstrap} from 'angular2/platform/browser'; import {ROUTER_PROVIDERS} from 'angular2/router'; import {AppComponent} from './app.component'; -// #enddocregion hash-strategy -import {HeroService} from './heroes/hero.service'; // #enddocregion v2 -// #docregion hash-strategy - // Add these symbols to register a `LocationStrategy` import {provide} from 'angular2/core'; import {LocationStrategy, @@ -21,27 +16,23 @@ import {LocationStrategy, // #enddocregion hash-strategy /* Can't use AppComponent ... but display as if we can -// #docregion v2,hash-strategy +// #docregion v2, hash-strategy bootstrap(AppComponent, [ -// #enddocregion v2,hash-strategy +// #enddocregion v2, hash-strategy */ // Actually use the v.2 component import {AppComponent as ac} from './app.component.2'; bootstrap(ac, [ +// #docregion v2, hash-strategy + ROUTER_PROVIDERS, +// #enddocregion v2, hash-strategy // #docregion hash-strategy - provide(LocationStrategy, - {useClass: HashLocationStrategy}), // ~/src/#/crisis-center/ - + {useClass: HashLocationStrategy}) // ~/#/crisis-center/ // #enddocregion hash-strategy -// #docregion v2 - HeroService, -// #docregion hash-strategy - ROUTER_PROVIDERS +// #docregion v2, hash-strategy ]); -// #enddocregion hash-strategy -// #enddocregion v2 - +// #enddocregion v2, hash-strategy diff --git a/public/docs/_examples/router/ts/app/boot.3.ts b/public/docs/_examples/router/ts/app/boot.3.ts index 3458b0e3b8..d895aff5e9 100644 --- a/public/docs/_examples/router/ts/app/boot.3.ts +++ b/public/docs/_examples/router/ts/app/boot.3.ts @@ -3,9 +3,5 @@ import {bootstrap} from 'angular2/platform/browser'; import {ROUTER_PROVIDERS} from 'angular2/router'; import {AppComponent} from './app.component.3'; -import {DialogService} from './dialog.service'; -bootstrap(AppComponent, [ - ROUTER_PROVIDERS, - DialogService -]); +bootstrap(AppComponent, [ROUTER_PROVIDERS]); diff --git a/public/docs/_examples/router/ts/app/boot.ts b/public/docs/_examples/router/ts/app/boot.ts index 05a9cfec23..6805a24f60 100644 --- a/public/docs/_examples/router/ts/app/boot.ts +++ b/public/docs/_examples/router/ts/app/boot.ts @@ -3,11 +3,5 @@ import {bootstrap} from 'angular2/platform/browser'; import {ROUTER_PROVIDERS} from 'angular2/router'; import {AppComponent} from './app.component'; -import {DialogService} from './dialog.service'; -import {HeroService} from './heroes/hero.service'; -bootstrap(AppComponent, [ - ROUTER_PROVIDERS, - DialogService, - HeroService -]); +bootstrap(AppComponent, [ROUTER_PROVIDERS]); diff --git a/public/docs/_examples/router/ts/app/crisis-center/add-crisis.component.ts b/public/docs/_examples/router/ts/app/crisis-center/add-crisis.component.ts index 48eae1be18..c9e2550eab 100644 --- a/public/docs/_examples/router/ts/app/crisis-center/add-crisis.component.ts +++ b/public/docs/_examples/router/ts/app/crisis-center/add-crisis.component.ts @@ -16,7 +16,7 @@ import {CanDeactivate, ComponentInstruction, Router} from 'angular2/router'; styles: ['input {width: 20em}'] }) export class AddCrisisComponent implements CanDeactivate { - public editName: string; + editName: string; constructor( private _service: CrisisService, diff --git a/public/docs/_examples/router/ts/app/crisis-center/crisis-center.component.ts b/public/docs/_examples/router/ts/app/crisis-center/crisis-center.component.ts index a342a18461..897d573d2d 100644 --- a/public/docs/_examples/router/ts/app/crisis-center/crisis-center.component.ts +++ b/public/docs/_examples/router/ts/app/crisis-center/crisis-center.component.ts @@ -22,8 +22,7 @@ import {CrisisService} from './crisis.service'; // #docregion default-route {path:'/', name: 'CrisisCenter', component: CrisisListComponent, useAsDefault: true}, // #enddocregion default-route - {path:'/:id', name: 'CrisisDetail', component: CrisisDetailComponent}, - {path:'/list/:id', name: 'CrisisList', component: CrisisListComponent} + {path:'/:id', name: 'CrisisDetail', component: CrisisDetailComponent} ]) // #enddocregion route-config export class CrisisCenterComponent { } diff --git a/public/docs/_examples/router/ts/app/crisis-center/crisis-detail.component.ts b/public/docs/_examples/router/ts/app/crisis-center/crisis-detail.component.ts index 06420c81c7..cba008126c 100644 --- a/public/docs/_examples/router/ts/app/crisis-center/crisis-detail.component.ts +++ b/public/docs/_examples/router/ts/app/crisis-center/crisis-detail.component.ts @@ -31,8 +31,8 @@ import {DialogService} from '../dialog.service'; // #docregion routerCanDeactivate, cancel-save export class CrisisDetailComponent implements OnInit, CanDeactivate { - public crisis: Crisis; - public editName: string; + crisis: Crisis; + editName: string; // #enddocregion routerCanDeactivate, cancel-save constructor( @@ -56,16 +56,17 @@ export class CrisisDetailComponent implements OnInit, CanDeactivate { } // #enddocregion ngOnInit - // #docregion canDeactivate - routerCanDeactivate(next: ComponentInstruction, prev: ComponentInstruction) { - // Allow navigation (`true`) if no crisis or the crisis is unchanged. + // #docregion routerCanDeactivate + routerCanDeactivate(next: ComponentInstruction, prev: ComponentInstruction) : any { + // Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged. + if (!this.crisis || this.crisis.name === this.editName) { + return true; + } // Otherwise ask the user with the dialog service and return its - // promise which resolves true-or-false when the user decides - return !this.crisis || - this.crisis.name === this.editName || - this._dialog.confirm('Discard changes?'); + // promise which resolves to true or false when the user decides + return this._dialog.confirm('Discard changes?'); } - // #enddocregion canDeactivate + // #enddocregion routerCanDeactivate // #docregion cancel-save cancel() { @@ -82,7 +83,9 @@ export class CrisisDetailComponent implements OnInit, CanDeactivate { // #docregion gotoCrises gotoCrises() { let route = - ['CrisisList', {id: this.crisis ? this.crisis.id : null} ] + // pass along the crisis id if available + // so that the CrisisList component can select that crisis + ['CrisisCenter', {id: this.crisis ? this.crisis.id : null} ] this._router.navigate(route); } diff --git a/public/docs/_examples/router/ts/app/crisis-center/crisis-list.component.ts b/public/docs/_examples/router/ts/app/crisis-center/crisis-list.component.ts index 9916027546..dc08f1e747 100644 --- a/public/docs/_examples/router/ts/app/crisis-center/crisis-list.component.ts +++ b/public/docs/_examples/router/ts/app/crisis-center/crisis-list.component.ts @@ -17,7 +17,7 @@ import {Router, RouteParams} from 'angular2/router'; `, }) export class CrisisListComponent implements OnInit { - public crises: Crisis[]; + crises: Crisis[]; // #docregion isSelected private _selectedId: number; diff --git a/public/docs/_examples/router/ts/app/heroes/hero-detail.component.ts b/public/docs/_examples/router/ts/app/heroes/hero-detail.component.ts index 4b4170d3d2..7d26ddafe7 100644 --- a/public/docs/_examples/router/ts/app/heroes/hero-detail.component.ts +++ b/public/docs/_examples/router/ts/app/heroes/hero-detail.component.ts @@ -19,7 +19,7 @@ import {RouteParams, Router} from 'angular2/router'; `, }) export class HeroDetailComponent implements OnInit { - public hero: Hero; + hero: Hero; // #docregion ctor constructor( diff --git a/public/docs/_examples/router/ts/app/heroes/hero-list.component.ts b/public/docs/_examples/router/ts/app/heroes/hero-list.component.ts index 6055c674f6..3a8de0408f 100644 --- a/public/docs/_examples/router/ts/app/heroes/hero-list.component.ts +++ b/public/docs/_examples/router/ts/app/heroes/hero-list.component.ts @@ -20,8 +20,8 @@ import {Router} from 'angular2/router'; // #enddocregion template }) export class HeroListComponent implements OnInit { - public heroes: Hero[]; - public selectedHero: Hero; + heroes: Hero[]; + selectedHero: Hero; // #docregion ctor constructor( diff --git a/public/docs/ts/latest/guide/router.jade b/public/docs/ts/latest/guide/router.jade index 244e90c943..92c9fb41f4 100644 --- a/public/docs/ts/latest/guide/router.jade +++ b/public/docs/ts/latest/guide/router.jade @@ -202,7 +202,7 @@ figure.image-display Both buttons navigate back to the *Crisis Center* and its list of crises. Suppose we click a crisis, make a change, but ***do not click either button***. - Maye we click the browser back button instead. Maybe we click the "Heroes" link. + Maybe we click the browser back button instead. Maybe we click the "Heroes" link. Do either. Up pops a dialog box. figure.image-display @@ -285,7 +285,7 @@ figure.image-display We should only need this trick for the live example, not production code. :marked ### Booting with the router service providers - Our app launches from the `boot.ts` file in the `~/app` folder so let's start there. + Our app launches from the `boot.ts` file in the `/app` folder so let's start there. It's short and all of it is relevant to routing. +makeExample('router/ts/app/boot.1.ts','all', 'boot.ts')(format=".") :marked @@ -438,7 +438,7 @@ figure.image-display That's unrealistic and ultimately not maintainable. We think it's better to put each feature area in its own folder. - Our first step is to **create a separate `app/heroes/` folder**. + Our first step is to **create a separate `app/heroes/` folder** and add *Hero Management* feature files there. We won't be creative about it. Our example is pretty much a @@ -467,8 +467,8 @@ figure.image-display .file hero-list.component.ts .file hero.service.ts :marked - We'll provide the `HeroService` during bootstrapping - so that is available everywhere in the app (see `boot.ts`) . + We provide the `HeroService` in the application root `AppComponent` + so that is available everywhere in the app. Now it's time for some surgery to bring these files and the rest of the app into alignment with our application router. @@ -663,8 +663,8 @@ code-example(format="." language="bash"). `AppComponent` route configuration. If we rename a *Crisis Center* component or change a route definition, we'll be changing the `AppComponent` too. - * If we followed *Heroes* lead, we'd be adding the `CrisisService` to the providers in `boot.ts`. - Now both `HeroService` and `CrisisService` would be available everywhere although + * If we followed *Heroes* lead, we'd be adding the `CrisisService` to the providers in `app.component.ts`. + Then both `HeroService` and `CrisisService` would be available everywhere although they're only needed in their respective feature modules. That stinks. Changes to a sub-module such as *Crisis Center* shouldn't provoke changes to the `AppComponent` or `boot.ts`. @@ -746,19 +746,28 @@ code-example(format="." language="bash"). ### Child Route Configuration The `CrisisCenterComponent` is a *Routing Component* like the `AppComponent`. - The `@RouteConfig` decorator that adorns the `CrisisCenterComponent` class defines routes in the same way + The `@RouteConfig` decorator that adorns the `CrisisCenterComponent` class defines routes in much the same way that we did earlier. +makeExample('router/ts/app/crisis-center/crisis-center.component.ts', 'route-config', 'app/crisis-center/crisis-center.component.ts (routes only)' )(format=".") :marked - There are three *Crisis Center* routes, two of them with an `id` parameter. - They refer to components we haven't talked about yet but whose purpose we - can guess by their names. + There is an *important difference in the paths*. They both begin at `/`. + Normally such paths would refer to the root of the application. + Here they refer to the **root of the child component!**. + + The Component Router composes the final route by concatenating route paths beginning with the ancestor paths to this child router. + In our example, there is one ancestor path: "crisis-center". + The final route to the `HeroDetailComponent` displaying hero 11 would be something like: +code-example(format=""). + localhost:3000//crisis-center/11 +:marked + We cannot know this simply by looking at the `CrisisCenterComponent` alone. + We can't tell that it is a *child* routing component. + We can't tell that its routes are child routes; they are indistinguiable from top level application routes. - We cannot tell by looking at the `CrisisCenterComponent` that it is a child component - of an application. We can't tell that its routes are child routes. - - That's intentional. The *Crisis Center* shouldn't know that it is the child of anything. - It might be the top level component of its own application. It might be repurposed in a different application. + Such ignorance is intentional. The *Crisis Center* shouldn't know that it is the child of anything. + Today it is a child component one level down. + Tomorrow it might be the top level component of its own application. + Next month it might be re-purposed in a different application. The *Crisis Center* itself is indifferent to these possibilities. *We* make it a child component of our application by reconfiguring the routes of the top level `AppComponent`. @@ -767,8 +776,9 @@ code-example(format="." language="bash"). Here is is the revised route configuration for the parent `AppComponent`: +makeExample('router/ts/app/app.component.ts', 'route-config', 'app/app.component.ts (routes only)' ) :marked - The second and third *Hero* routes haven't changed. - The first *Crisis Center* route has changed — *signficantly* — and we've formatted it to draw attention to the differences: + The last two *Hero* routes haven't changed. + + The first *Crisis Center* route has changed — *significantly* — and we've formatted it to draw attention to the differences: +makeExample('router/ts/app/app.component.ts', 'route-config-cc')(format=".") :marked Notice that the **path ends with a slash and three trailing periods (`/...`)**. @@ -794,7 +804,7 @@ code-example(format="." language="bash"). It could be any of the three. In the absence of additional information, the router can't decide and must throw an error. - We've tried the sample application and it didn't fail. We must have done something. + We've tried the sample application and it didn't fail. We must have done something right. Scroll to the end of the child `CrisisCenterComponent`s first route. +makeExample('router/ts/app/crisis-center/crisis-center.component.ts', 'default-route', 'app/crisis-center/crisis-center.component.ts (default route)')(format=".") @@ -879,9 +889,11 @@ code-example(format=""). by waiting for the user's answer asynchronously. Waiting for the user asynchronously is like waiting for the server asynchronously. :marked - The dialog service returns a [promise](http://www.html5rocks.com/en/tutorials/es6/promises/). - The promise *resolves* when the user eventually decides - to discard changes and navigate away (`true`) or keep the pending changes and stay in the crisis editor (`false`). + The `DialogService` (injected in the `AppComponent` for app-wide use) does the asking. + + It returns a [promise](http://www.html5rocks.com/en/tutorials/es6/promises/) that + *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`). @@ -890,16 +902,17 @@ code-example(format=""). +makeExample('router/ts/app/crisis-center/crisis-detail.component.ts', 'routerCanDeactivate', 'crisis-detail.component.ts (excerpt)') :marked Notice that the `routerCanDeactivate` method *can* return synchronously; - it returns `true` immediately if there are no pending changes. - But it can also return a promise and the router will wait for that promise - to resolve before navigating away or staying put. + it returns `true` immediately if there is no crisis or there are no pending changes. + But it can also return a promise and the router will wait for that promise to resolve to truthy (navigate) or falsey (stay put). **Two critical points** - 1. The router hook is optional. We don't inherit from a base class. We simply implement the method or not. + 1. The router hook is optional. We don't inherit from a base class. We simply implement the interface method or not. 1. We rely on the router to call the hook. We don't worry about all the ways that the user could navigate away. That's the router's job. We simply write this method and let the router take it from there. + + The final code for the *Crisis Center* feature is [here](#crisis-center-structure-and-code). .l-main-section