docs(router): add query params milestone
Additional fixes suggested by recent issues. closes #637
This commit is contained in:
parent
8e3e0834b5
commit
af458737be
|
@ -6,11 +6,22 @@ import {Component} from 'angular2/core';
|
|||
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
|
||||
|
||||
import {CrisisListComponent} from './crisis-list.component';
|
||||
// #enddocregion
|
||||
/*
|
||||
// Apparent Milestone 2 imports
|
||||
// #docregion
|
||||
// #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
|
||||
// #enddocregion
|
||||
*/
|
||||
// Actual Milestone 2 imports
|
||||
import {HeroListComponent} from './heroes/hero-list.component.1';
|
||||
import {HeroDetailComponent} from './heroes/hero-detail.component.1';
|
||||
import {HeroService} from './heroes/hero.service';
|
||||
// #docregion
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
|
@ -45,6 +56,3 @@ import {HeroService} from './heroes/hero.service';
|
|||
export class AppComponent { }
|
||||
// #enddocregion route-config
|
||||
// #enddocregion
|
||||
|
||||
// #docregion child-router-link
|
||||
// #enddocregion child-router-link
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import {Component} from 'angular2/core';
|
||||
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
|
||||
|
||||
import {CrisisCenterComponent} from './crisis-center/crisis-center.component';
|
||||
import {HeroListComponent} from './heroes/hero-list.component';
|
||||
import {HeroDetailComponent} from './heroes/hero-detail.component';
|
||||
import {CrisisCenterComponent} from './crisis-center/crisis-center.component.1';
|
||||
import {HeroListComponent} from './heroes/hero-list.component.1';
|
||||
import {HeroDetailComponent} from './heroes/hero-detail.component.1';
|
||||
|
||||
import {DialogService} from './dialog.service';
|
||||
import {HeroService} from './heroes/hero.service';
|
||||
|
@ -36,7 +35,6 @@ import {HeroService} from './heroes/hero.service';
|
|||
<a [routerLink]="['CrisisCenter', 'CrisisDetail', {id:1}]">Princess Crisis</a>
|
||||
// #enddocregion princess-anchor
|
||||
*/
|
||||
// #docregion
|
||||
// #docregion template
|
||||
template: `
|
||||
<h1 class="title">Component Router</h1>
|
||||
|
|
|
@ -3,36 +3,30 @@
|
|||
// Also includes digression on HashPathStrategy (not used in the final app)
|
||||
// #docplaster
|
||||
|
||||
// #docregion v2
|
||||
// #docregion
|
||||
import {bootstrap} from 'angular2/platform/browser';
|
||||
import {ROUTER_PROVIDERS} from 'angular2/router';
|
||||
import {AppComponent} from './app.component';
|
||||
// #enddocregion v2
|
||||
|
||||
// Add these symbols to register a `LocationStrategy`
|
||||
// Add these symbols to override the `LocationStrategy`
|
||||
import {provide} from 'angular2/core';
|
||||
import {LocationStrategy,
|
||||
HashLocationStrategy} from 'angular2/router';
|
||||
// #enddocregion hash-strategy
|
||||
|
||||
// #enddocregion
|
||||
/* Can't use AppComponent ... but display as if we can
|
||||
// #docregion v2, hash-strategy
|
||||
// #docregion
|
||||
|
||||
bootstrap(AppComponent, [
|
||||
// #enddocregion v2, hash-strategy
|
||||
// #enddocregion
|
||||
*/
|
||||
|
||||
// Actually use the v.2 component
|
||||
import {AppComponent as ac} from './app.component.2';
|
||||
|
||||
bootstrap(ac, [
|
||||
// #docregion v2, hash-strategy
|
||||
// #docregion
|
||||
ROUTER_PROVIDERS,
|
||||
// #enddocregion v2, hash-strategy
|
||||
// #docregion hash-strategy
|
||||
provide(LocationStrategy,
|
||||
{useClass: HashLocationStrategy}) // ~/#/crisis-center/
|
||||
// #enddocregion hash-strategy
|
||||
// #docregion v2, hash-strategy
|
||||
{useClass: HashLocationStrategy}) // .../#/crisis-center/
|
||||
]);
|
||||
// #enddocregion v2, hash-strategy
|
||||
// #enddocregion
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import {Component} from 'angular2/core';
|
||||
import {RouteConfig, RouterOutlet} from 'angular2/router';
|
||||
|
||||
import {CrisisListComponent} from './crisis-list.component.1';
|
||||
import {CrisisDetailComponent} from './crisis-detail.component.1';
|
||||
import {CrisisService} from './crisis.service';
|
||||
|
||||
// #docregion minus-imports
|
||||
@Component({
|
||||
template: `
|
||||
<h2>CRISIS CENTER</h2>
|
||||
<router-outlet></router-outlet>
|
||||
`,
|
||||
directives: [RouterOutlet],
|
||||
// #docregion providers
|
||||
providers: [CrisisService]
|
||||
// #enddocregion providers
|
||||
})
|
||||
// #docregion route-config
|
||||
@RouteConfig([
|
||||
// #docregion default-route
|
||||
{path:'/', name: 'CrisisCenter', component: CrisisListComponent, useAsDefault: true},
|
||||
// #enddocregion default-route
|
||||
{path:'/:id', name: 'CrisisDetail', component: CrisisDetailComponent}
|
||||
])
|
||||
// #enddocregion route-config
|
||||
export class CrisisCenterComponent { }
|
||||
// #enddocregion minus-imports
|
|
@ -6,25 +6,17 @@ import {CrisisListComponent} from './crisis-list.component';
|
|||
import {CrisisDetailComponent} from './crisis-detail.component';
|
||||
import {CrisisService} from './crisis.service';
|
||||
|
||||
// #docregion minus-imports
|
||||
@Component({
|
||||
template: `
|
||||
<h2>CRISIS CENTER</h2>
|
||||
<router-outlet></router-outlet>
|
||||
`,
|
||||
directives: [RouterOutlet],
|
||||
// #docregion providers
|
||||
providers: [CrisisService]
|
||||
// #enddocregion providers
|
||||
})
|
||||
// #docregion route-config
|
||||
@RouteConfig([
|
||||
// #docregion default-route
|
||||
{path:'/', name: 'CrisisCenter', component: CrisisListComponent, useAsDefault: true},
|
||||
// #enddocregion default-route
|
||||
{path:'/:id', name: 'CrisisDetail', component: CrisisDetailComponent}
|
||||
])
|
||||
// #enddocregion route-config
|
||||
export class CrisisCenterComponent { }
|
||||
// #enddocregion minus-imports
|
||||
// #enddocregion
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
// #docplaster
|
||||
|
||||
// #docregion
|
||||
import {Component, OnInit} from 'angular2/core';
|
||||
import {Crisis, CrisisService} from './crisis.service';
|
||||
import {RouteParams, Router} from 'angular2/router';
|
||||
// #docregion routerCanDeactivate
|
||||
import {CanDeactivate, ComponentInstruction} from 'angular2/router';
|
||||
import {DialogService} from '../dialog.service';
|
||||
|
||||
// #enddocregion routerCanDeactivate
|
||||
|
||||
@Component({
|
||||
// #docregion template
|
||||
template: `
|
||||
<div *ngIf="crisis">
|
||||
<h3>"{{editName}}"</h3>
|
||||
<div>
|
||||
<label>Id: </label>{{crisis.id}}</div>
|
||||
<div>
|
||||
<label>Name: </label>
|
||||
<input [(ngModel)]="editName" placeholder="name"/>
|
||||
</div>
|
||||
<button (click)="save()">Save</button>
|
||||
<button (click)="cancel()">Cancel</button>
|
||||
</div>
|
||||
`,
|
||||
// #enddocregion template
|
||||
styles: ['input {width: 20em}']
|
||||
})
|
||||
// #docregion routerCanDeactivate, cancel-save
|
||||
export class CrisisDetailComponent implements OnInit, CanDeactivate {
|
||||
|
||||
crisis: Crisis;
|
||||
editName: string;
|
||||
|
||||
// #enddocregion routerCanDeactivate, cancel-save
|
||||
constructor(
|
||||
private _service: CrisisService,
|
||||
private _router: Router,
|
||||
private _routeParams: RouteParams,
|
||||
private _dialog: DialogService
|
||||
) { }
|
||||
|
||||
// #docregion ngOnInit
|
||||
ngOnInit() {
|
||||
let id = +this._routeParams.get('id');
|
||||
this._service.getCrisis(id).then(crisis => {
|
||||
if (crisis) {
|
||||
this.editName = crisis.name;
|
||||
this.crisis = crisis;
|
||||
} else { // id not found
|
||||
this.gotoCrises();
|
||||
}
|
||||
});
|
||||
}
|
||||
// #enddocregion ngOnInit
|
||||
|
||||
// #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 to true or false when the user decides
|
||||
return this._dialog.confirm('Discard changes?');
|
||||
}
|
||||
// #enddocregion routerCanDeactivate
|
||||
|
||||
// #docregion cancel-save
|
||||
cancel() {
|
||||
this.editName = this.crisis.name;
|
||||
this.gotoCrises();
|
||||
}
|
||||
|
||||
save() {
|
||||
this.crisis.name = this.editName;
|
||||
this.gotoCrises();
|
||||
}
|
||||
// #enddocregion cancel-save
|
||||
|
||||
// #docregion gotoCrises
|
||||
gotoCrises() {
|
||||
// Like <a [routerLink]="['CrisisCenter']">Crisis Center</a
|
||||
this._router.navigate(['CrisisCenter']);
|
||||
}
|
||||
// #enddocregion gotoCrises
|
||||
// #docregion routerCanDeactivate, cancel-save
|
||||
}
|
||||
// #enddocregion routerCanDeactivate, cancel-save
|
||||
// #enddocregion
|
|
@ -4,14 +4,10 @@
|
|||
import {Component, OnInit} from 'angular2/core';
|
||||
import {Crisis, CrisisService} from './crisis.service';
|
||||
import {RouteParams, Router} from 'angular2/router';
|
||||
// #docregion routerCanDeactivate
|
||||
import {CanDeactivate, ComponentInstruction} from 'angular2/router';
|
||||
import {DialogService} from '../dialog.service';
|
||||
|
||||
// #enddocregion routerCanDeactivate
|
||||
|
||||
@Component({
|
||||
// #docregion template
|
||||
template: `
|
||||
<div *ngIf="crisis">
|
||||
<h3>"{{editName}}"</h3>
|
||||
|
@ -25,16 +21,14 @@ import {DialogService} from '../dialog.service';
|
|||
<button (click)="cancel()">Cancel</button>
|
||||
</div>
|
||||
`,
|
||||
// #enddocregion template
|
||||
styles: ['input {width: 20em}']
|
||||
})
|
||||
// #docregion routerCanDeactivate, cancel-save
|
||||
|
||||
export class CrisisDetailComponent implements OnInit, CanDeactivate {
|
||||
|
||||
crisis: Crisis;
|
||||
editName: string;
|
||||
|
||||
// #enddocregion routerCanDeactivate, cancel-save
|
||||
constructor(
|
||||
private _service: CrisisService,
|
||||
private _router: Router,
|
||||
|
@ -42,7 +36,6 @@ export class CrisisDetailComponent implements OnInit, CanDeactivate {
|
|||
private _dialog: DialogService
|
||||
) { }
|
||||
|
||||
// #docregion ngOnInit
|
||||
ngOnInit() {
|
||||
let id = +this._routeParams.get('id');
|
||||
this._service.getCrisis(id).then(crisis => {
|
||||
|
@ -54,9 +47,7 @@ export class CrisisDetailComponent implements OnInit, CanDeactivate {
|
|||
}
|
||||
});
|
||||
}
|
||||
// #enddocregion ngOnInit
|
||||
|
||||
// #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) {
|
||||
|
@ -66,9 +57,7 @@ export class CrisisDetailComponent implements OnInit, CanDeactivate {
|
|||
// promise which resolves to true or false when the user decides
|
||||
return this._dialog.confirm('Discard changes?');
|
||||
}
|
||||
// #enddocregion routerCanDeactivate
|
||||
|
||||
// #docregion cancel-save
|
||||
cancel() {
|
||||
this.editName = this.crisis.name;
|
||||
this.gotoCrises();
|
||||
|
@ -78,19 +67,16 @@ export class CrisisDetailComponent implements OnInit, CanDeactivate {
|
|||
this.crisis.name = this.editName;
|
||||
this.gotoCrises();
|
||||
}
|
||||
// #enddocregion cancel-save
|
||||
|
||||
// #docregion gotoCrises
|
||||
gotoCrises() {
|
||||
let route =
|
||||
// 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);
|
||||
let crisisId = this.crisis ? this.crisis.id : null;
|
||||
// Pass along the hero id if available
|
||||
// so that the CrisisListComponent can select that hero.
|
||||
// Add a totally useless `foo` parameter for kicks.
|
||||
// #docregion gotoCrises-navigate
|
||||
this._router.navigate(['CrisisCenter', {id: crisisId, foo: 'foo'} ]);
|
||||
// #enddocregion gotoCrises-navigate
|
||||
}
|
||||
// #enddocregion gotoCrises
|
||||
// #docregion routerCanDeactivate, cancel-save
|
||||
}
|
||||
// #enddocregion routerCanDeactivate, cancel-save
|
||||
// #enddocregion
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// #docplaster
|
||||
|
||||
// #docregion
|
||||
import {Component, OnInit} from 'angular2/core';
|
||||
import {Crisis, CrisisService} from './crisis.service';
|
||||
import {Router} from 'angular2/router';
|
||||
|
||||
@Component({
|
||||
// #docregion template
|
||||
template: `
|
||||
<ul>
|
||||
<li *ngFor="#crisis of crises"
|
||||
(click)="onSelect(crisis)">
|
||||
<span class="badge">{{crisis.id}}</span> {{crisis.name}}
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
// #enddocregion template
|
||||
})
|
||||
export class CrisisListComponent implements OnInit {
|
||||
crises: Crisis[];
|
||||
|
||||
constructor(
|
||||
private _service: CrisisService,
|
||||
private _router: Router) {}
|
||||
|
||||
ngOnInit() {
|
||||
this._service.getCrises().then(crises => this.crises = crises);
|
||||
}
|
||||
|
||||
// #docregion select
|
||||
onSelect(crisis: Crisis) {
|
||||
this._router.navigate(['CrisisDetail', { id: crisis.id }] );
|
||||
}
|
||||
// #enddocregion select
|
||||
}
|
|
@ -18,7 +18,7 @@ import {Router, RouteParams} from 'angular2/router';
|
|||
})
|
||||
export class CrisisListComponent implements OnInit {
|
||||
crises: Crisis[];
|
||||
// #docregion isSelected
|
||||
|
||||
private _selectedId: number;
|
||||
|
||||
constructor(
|
||||
|
@ -27,19 +27,14 @@ export class CrisisListComponent implements OnInit {
|
|||
routeParams: RouteParams) {
|
||||
this._selectedId = +routeParams.get('id');
|
||||
}
|
||||
// #enddocregion isSelected
|
||||
|
||||
isSelected(crisis: Crisis) { return crisis.id === this._selectedId; }
|
||||
|
||||
ngOnInit() {
|
||||
this._service.getCrises().then(crises => this.crises = crises);
|
||||
}
|
||||
// #docregion isSelected
|
||||
|
||||
isSelected(crisis: Crisis) { return crisis.id === this._selectedId; }
|
||||
// #enddocregion isSelected
|
||||
|
||||
// #docregion select
|
||||
onSelect(crisis: Crisis) {
|
||||
this._router.navigate( ['CrisisDetail', { id: crisis.id }] );
|
||||
}
|
||||
// #enddocregion select
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// #docregion
|
||||
import {Component, OnInit} from 'angular2/core';
|
||||
import {Hero, HeroService} from './hero.service';
|
||||
import {RouteParams, Router} from 'angular2/router';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h2>HEROES</h2>
|
||||
<div *ngIf="hero">
|
||||
<h3>"{{hero.name}}"</h3>
|
||||
<div>
|
||||
<label>Id: </label>{{hero.id}}</div>
|
||||
<div>
|
||||
<label>Name: </label>
|
||||
<input [(ngModel)]="hero.name" placeholder="name"/>
|
||||
</div>
|
||||
<button (click)="gotoHeroes()">Back</button>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class HeroDetailComponent implements OnInit {
|
||||
hero: Hero;
|
||||
|
||||
// #docregion ctor
|
||||
constructor(
|
||||
private _router:Router,
|
||||
private _routeParams:RouteParams,
|
||||
private _service:HeroService){}
|
||||
// #enddocregion ctor
|
||||
|
||||
// #docregion ngOnInit
|
||||
ngOnInit() {
|
||||
let id = this._routeParams.get('id');
|
||||
this._service.getHero(id).then(hero => this.hero = hero);
|
||||
}
|
||||
// #enddocregion ngOnInit
|
||||
|
||||
// #docregion gotoHeroes
|
||||
gotoHeroes() {
|
||||
// Like <a [routerLink]="['Heroes']">Heroes</a>
|
||||
this._router.navigate(['Heroes']);
|
||||
}
|
||||
// #enddocregion gotoHeroes
|
||||
}
|
|
@ -37,8 +37,13 @@ export class HeroDetailComponent implements OnInit {
|
|||
|
||||
// #docregion gotoHeroes
|
||||
gotoHeroes() {
|
||||
// <a [routerLink]="['Heroes']">Heroes</a>
|
||||
this._router.navigate(['Heroes']);
|
||||
let heroId = this.hero ? this.hero.id : null;
|
||||
// Pass along the hero id if available
|
||||
// so that the HeroList component can select that hero.
|
||||
// Add a totally useless `foo` parameter for kicks.
|
||||
// #docregion gotoHeroes-navigate
|
||||
this._router.navigate(['Heroes', {id: heroId, foo: 'foo'} ]);
|
||||
// #enddocregion gotoHeroes-navigate
|
||||
}
|
||||
// #enddocregion gotoHeroes
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
// #docplaster
|
||||
|
||||
// #docregion
|
||||
// TODO SOMEDAY: Feature Componetized like HeroCenter
|
||||
import {Component, OnInit} from 'angular2/core';
|
||||
import {Hero, HeroService} from './hero.service';
|
||||
import {Router} from 'angular2/router';
|
||||
|
||||
@Component({
|
||||
// #docregion template
|
||||
template: `
|
||||
<h2>HEROES</h2>
|
||||
<ul>
|
||||
<li *ngFor="#hero of heroes"
|
||||
(click)="onSelect(hero)">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
</ul>
|
||||
`
|
||||
// #enddocregion template
|
||||
})
|
||||
export class HeroListComponent implements OnInit {
|
||||
heroes: Hero[];
|
||||
|
||||
// #docregion ctor
|
||||
constructor(
|
||||
private _router: Router,
|
||||
private _service: HeroService) { }
|
||||
// #enddocregion ctor
|
||||
|
||||
ngOnInit() {
|
||||
this._service.getHeroes().then(heroes => this.heroes = heroes)
|
||||
}
|
||||
|
||||
// #docregion select
|
||||
onSelect(hero: Hero) {
|
||||
// #docregion nav-to-detail
|
||||
this._router.navigate( ['HeroDetail', { id: hero.id }] );
|
||||
// #enddocregion nav-to-detail
|
||||
}
|
||||
// #enddocregion select
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
/* A link parameters array
|
||||
// #docregion link-parameters-array
|
||||
['HeroDetail', { id: hero.id }] // {id: 15}
|
||||
// #enddocregion link-parameters-array
|
||||
*/
|
|
@ -1,10 +1,12 @@
|
|||
// #docplaster
|
||||
|
||||
// TODO SOMEDAY: Feature Componetized like CrisisCenter
|
||||
// #docregion
|
||||
// TODO SOMEDAY: Feature Componetized like HeroCenter
|
||||
import {Component, OnInit} from 'angular2/core';
|
||||
import {Hero, HeroService} from './hero.service';
|
||||
import {Router} from 'angular2/router';
|
||||
// #docregion import-route-params
|
||||
import {Router, RouteParams} from 'angular2/router';
|
||||
// #enddocregion import-route-params
|
||||
|
||||
@Component({
|
||||
// #docregion template
|
||||
|
@ -12,6 +14,7 @@ import {Router} from 'angular2/router';
|
|||
<h2>HEROES</h2>
|
||||
<ul>
|
||||
<li *ngFor="#hero of heroes"
|
||||
[class.selected]="isSelected(hero)"
|
||||
(click)="onSelect(hero)">
|
||||
<span class="badge">{{hero.id}}</span> {{hero.name}}
|
||||
</li>
|
||||
|
@ -21,29 +24,30 @@ import {Router} from 'angular2/router';
|
|||
})
|
||||
export class HeroListComponent implements OnInit {
|
||||
heroes: Hero[];
|
||||
selectedHero: Hero;
|
||||
|
||||
// #docregion ctor
|
||||
private _selectedId: number;
|
||||
|
||||
constructor(
|
||||
private _service: HeroService,
|
||||
private _router: Router,
|
||||
private _service: HeroService) { }
|
||||
routeParams: RouteParams) {
|
||||
this._selectedId = +routeParams.get('id');
|
||||
}
|
||||
// #enddocregion ctor
|
||||
|
||||
// #docregion isSelected
|
||||
isSelected(hero: Hero) { return hero.id === this._selectedId; }
|
||||
// #enddocregion isSelected
|
||||
|
||||
// #docregion select
|
||||
onSelect(hero: Hero) {
|
||||
this._router.navigate( ['HeroDetail', { id: hero.id }] );
|
||||
}
|
||||
// #enddocregion select
|
||||
|
||||
ngOnInit() {
|
||||
this._service.getHeroes().then(heroes => this.heroes = heroes)
|
||||
}
|
||||
// #docregion select
|
||||
onSelect(hero: Hero) {
|
||||
// #docregion nav-to-detail
|
||||
this._router.navigate( ['HeroDetail', { id: hero.id }] );
|
||||
// #enddocregion nav-to-detail
|
||||
}
|
||||
// #enddocregion select
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
/* A link parameters array
|
||||
// #docregion link-parameters-array
|
||||
['HeroDetail', { id: hero.id }] // {id: 15}
|
||||
// #enddocregion link-parameters-array
|
||||
*/
|
|
@ -1,7 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<script>
|
||||
var boot = 'app/boot'+'.1'; // choices: '.1', '.2', '.3', ''
|
||||
</script>
|
||||
<!-- #docregion -->
|
||||
<html>
|
||||
<head>
|
||||
|
@ -19,26 +16,20 @@
|
|||
<!-- #enddocregion router-lib -->
|
||||
<script>
|
||||
System.config({
|
||||
packages: {
|
||||
packages: {
|
||||
app: {
|
||||
format: 'register',
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
}
|
||||
});
|
||||
System.import('app/boot')
|
||||
System.import('app/boot.1') // <----- ONLY CHANGE
|
||||
.then(null, console.error.bind(console));
|
||||
</script>
|
||||
<script>
|
||||
System.config({
|
||||
packages: {'app': {defaultExtension: 'js'}}
|
||||
});
|
||||
// window.boot is for our testing; you should just use 'app/boot'
|
||||
System.import(window.boot||'app/boot').catch(console.log.bind(console));
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Milestone 1</h1>
|
||||
<my-app>loading...</my-app>
|
||||
</body>
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- #docregion -->
|
||||
<html>
|
||||
<head>
|
||||
<!-- #docregion base-href -->
|
||||
<base href="/">
|
||||
<!-- #enddocregion base-href -->
|
||||
<title>Router Sample</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
<!-- #docregion router-lib -->
|
||||
<script src="node_modules/angular2/bundles/router.dev.js"></script>
|
||||
<!-- #enddocregion router-lib -->
|
||||
<script>
|
||||
System.config({
|
||||
packages: {
|
||||
app: {
|
||||
format: 'register',
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
}
|
||||
});
|
||||
System.import('app/boot.2') // <----- ONLY CHANGE
|
||||
.then(null, console.error.bind(console));
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Milestone 2</h1>
|
||||
<my-app>loading...</my-app>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<!-- #enddocregion -->
|
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- #docregion -->
|
||||
<html>
|
||||
<head>
|
||||
<!-- #docregion base-href -->
|
||||
<base href="/">
|
||||
<!-- #enddocregion base-href -->
|
||||
<title>Router Sample</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="node_modules/rxjs/bundles/Rx.js"></script>
|
||||
<script src="node_modules/angular2/bundles/angular2.dev.js"></script>
|
||||
<!-- #docregion router-lib -->
|
||||
<script src="node_modules/angular2/bundles/router.dev.js"></script>
|
||||
<!-- #enddocregion router-lib -->
|
||||
<script>
|
||||
System.config({
|
||||
packages: {
|
||||
app: {
|
||||
format: 'register',
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
}
|
||||
});
|
||||
System.import('app/boot.3') // <----- ONLY CHANGE
|
||||
.then(null, console.error.bind(console));
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Milestone 3</h1>
|
||||
<my-app>loading...</my-app>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<!-- #enddocregion -->
|
|
@ -20,7 +20,9 @@ a:hover {color: white; background-color: #1171a3; }
|
|||
a.router-link-active {color: white; background-color: #52b9e9; }
|
||||
|
||||
/* #enddocregion starter */
|
||||
/* #docregion selected */
|
||||
.selected { background-color: #EEE; color: #369; }
|
||||
/* #enddocregion selected */
|
||||
|
||||
.badge {
|
||||
font-size: small;
|
||||
|
|
|
@ -19,15 +19,52 @@ include ../../../../_includes/_util-fns
|
|||
We can navigate imperatively when the user clicks a button, selects from a drop box,
|
||||
or in response to some other stimulus from any source. And the router logs activity
|
||||
in the browser's history journal so the back and forward buttons work as well.
|
||||
|
||||
We'll learn many router details in this chapter which covers
|
||||
|
||||
* [configuring a router](#route-config)
|
||||
* the [link parameter arrays](#link-parameters-array) that propel router navigation
|
||||
* navigating when the user clicks a data-bound [RouterLink](#router-link)
|
||||
* navigating under [program control](#navigate)
|
||||
* embedding critical information in the URL with [route parameters](#route-parameters)
|
||||
* creating a [child router](#child-router) with its own routes
|
||||
* setting a [default route](#default)
|
||||
* confirming or canceling navigation with [router lifecycle hooks](#lifecycle-hooks)
|
||||
* passing optional information in [query parameters](#query-parameters)
|
||||
|
||||
We proceed in phases marked by milestones building rom a simple two-pager with placeholder views
|
||||
up to a modular, multi-view design with child routes.
|
||||
|
||||
Try that [live final version](/resources/live-examples/router/ts/plnkr.html).
|
||||
|
||||
But first, an overview of router basics.
|
||||
|
||||
[Live Example](/resources/live-examples/router/ts/plnkr.html).
|
||||
.l-main-section
|
||||
:marked
|
||||
## The Basics
|
||||
Let's begin with a few core concepts of the Component Router.
|
||||
Then we can explore the details through a sequence of examples.
|
||||
### Setup
|
||||
The Component Router is an optional service that presents a particular component view for a given URL.
|
||||
It is not part of the Angular 2 core. It is in its own library
|
||||
within the Angular npm bundle.
|
||||
We make it available by loading its script in our `index.html`, after
|
||||
the Angular core script.
|
||||
+makeExample('router/ts/index.html','router-lib')(format=".")
|
||||
:marked
|
||||
### *<base href>*
|
||||
Most routing applications should add a `<base>` element just after the `<head>` tag
|
||||
to tell the router how to compose navigation URLs.
|
||||
|
||||
The **`Router`** is a service that presents a particular Component view for a given URL.
|
||||
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.html','base-href')(format=".")
|
||||
.l-sub-section
|
||||
:marked
|
||||
We cover other options in the [details below](#browser-url-styles).
|
||||
:marked
|
||||
### Configuration
|
||||
When the browser's URL changes, the router looks for a corresponding **`RouteDefinition`**
|
||||
from which it can determine the Component to display.
|
||||
|
||||
|
@ -51,6 +88,7 @@ include ../../../../_includes/_util-fns
|
|||
will use that value to find and present the hero whose `id` is 42.
|
||||
We'll learn more about route parameters later in this chapter.
|
||||
:marked
|
||||
### Router Outlet
|
||||
Now we know how the router gets its configuration.
|
||||
When the browser URL for this application becomes `/heroes`,
|
||||
the router matches that URL to the `RouteDefintion` named *Heroes* and displays the `HeroListComponent`
|
||||
|
@ -59,6 +97,7 @@ code-example(format="", language="html").
|
|||
<!-- Routed views go here -->
|
||||
<router-outlet></router-outlet>
|
||||
:marked
|
||||
### Router Links
|
||||
Now we have routes configured and a place to render them, but
|
||||
how do we navigate? The URL could arrive directly from the browser address bar.
|
||||
But most of the time we navigate as a result of some user action such as the click of
|
||||
|
@ -77,7 +116,7 @@ code-example(format="", language="html").
|
|||
'CrisisCenter' and 'Heroes' are the names of the `Routes` we configured above.
|
||||
|
||||
We'll learn to write more complex link expressions — and why they are arrays —
|
||||
[later](#link-parameter-array) in the chapter.
|
||||
[later](#link-parameters-array) in the chapter.
|
||||
:marked
|
||||
### Let's summarize
|
||||
|
||||
|
@ -131,38 +170,25 @@ table
|
|||
td.
|
||||
An Angular component with an attached router.
|
||||
:marked
|
||||
We'll learn many more details in this chapter which covers
|
||||
|
||||
* [configuring a router](#route-config)
|
||||
* the [link parameter arrays](#link-parameters-array) that propel router navigation
|
||||
* navigating when the user clicks a data-bound [RouterLink](#router-link)
|
||||
* navigating under [program control](#navigate)
|
||||
* passing information in [route parameters](#route-parameter)
|
||||
* creating a [child router](#child-router) with its own routes
|
||||
* setting a [default route](#default)
|
||||
* pausing, confirming and/or canceling a navigation with the the `routerCanDeactivate` [router lifecycle hook](#lifecycle-hooks)
|
||||
|
||||
We proceed in phases marked by milestones.
|
||||
Our first milestone is the ability to navigate between two placeholder views.
|
||||
At our last milestone, we'll have a modular, multi-view design with child routes.
|
||||
|
||||
We assume that you're already comfortable with the basic Angular 2 tools and concepts
|
||||
we introduced in the [QuickStart](../quickstart.html) and
|
||||
the [Tour of Heroes](../tutorial/) tutorial.
|
||||
.l-sub-section
|
||||
:marked
|
||||
While we make incremental progress on a sample application, this chapter is not a tutorial.
|
||||
We discuss code and design decisions pertinent to routing and application design.
|
||||
We gloss over everything in between.
|
||||
|
||||
The full source is available in the [live example](/resources/live-examples/router/ts/plnkr.html).
|
||||
:marked
|
||||
|
||||
We've barely touched the surface of the router and its capabilities.
|
||||
|
||||
The following detail sections describe a sample routing application
|
||||
as it evolves over a sequence of milestones.
|
||||
We strongly recommend taking the time to read and understand this story.
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## The Sample Application
|
||||
We have an application in mind as we move from milestone to milestone.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
While we make incremental progress toward the ultimate sample application, this chapter is not a tutorial.
|
||||
We discuss code and design decisions pertinent to routing and application design.
|
||||
We gloss over everything in between.
|
||||
|
||||
The full source is available in the [live example](/resources/live-examples/router/ts/plnkr.html).
|
||||
:marked
|
||||
Our client is the Hero Employment Agency.
|
||||
Heroes need work and The Agency finds Crises for them to solve.
|
||||
|
||||
|
@ -245,6 +271,7 @@ figure.image-display
|
|||
The Component Router library is part of the Angular npm bundle.
|
||||
We make it available by loading its script in our `index.html`, right after
|
||||
the Angular core script.
|
||||
<a id="base-href"></a>
|
||||
+makeExample('router/ts/index.html','router-lib')(format=".")
|
||||
:marked
|
||||
### Set the *<base href>*
|
||||
|
@ -503,13 +530,13 @@ figure.image-display
|
|||
Notice the `:id` token in the the path. That creates a slot in the path for a **Route Parameter**.
|
||||
In this case, we're expecting the router to insert the `id` of a hero into that slot.
|
||||
|
||||
If we tell the router to navigate to the detail component and display "Magenta", we expect hero `id` (15) to appear in the
|
||||
If we tell the router to navigate to the detail component and display "Magneta", we expect hero `id` (15) to appear in the
|
||||
browser URL like this:
|
||||
code-example(format="." language="bash").
|
||||
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 "Magenta" detail view.
|
||||
pattern and go to the same "Magneta" detail view.
|
||||
|
||||
<a id="navigate"></id>
|
||||
### Navigate to the detail imperatively
|
||||
|
@ -522,28 +549,29 @@ code-example(format="." language="bash").
|
|||
|
||||
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.ts','ctor')(format=".")
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.1.ts','ctor')(format=".")
|
||||
:marked
|
||||
We make a few changes to the template:
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.ts','template')(format=".")
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.1.ts','template')(format=".")
|
||||
: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.ts','select')(format=".")
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.1.ts','select')(format=".")
|
||||
:marked
|
||||
It calls the router's **`navigate`** method with a **Link Parameters Array**.
|
||||
This array is similar to the *link parameters array* we met [earlier](#shell-template) in an anchor tag while
|
||||
binding to the `RouterLink` directive. This time we see it in code rather than in HTML.
|
||||
<a id="route-parameter"></id>
|
||||
### Setting the route parameter
|
||||
|
||||
<a id="route-parameters"></id>
|
||||
### Setting the route parameters object
|
||||
|
||||
We're navigating to the `HeroDetailComponent` where we expect to see the details of the selected hero.
|
||||
We'll need *two* pieces of information: the destination and the hero's `id`.
|
||||
|
||||
Accordingly, the *link parameters array* has *two* items: the **name** of the destination route and a **route parameters object** that specifies the
|
||||
`id` of the selected hero.
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.ts','link-parameters-array')(format=".")
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.1.ts','link-parameters-array')(format=".")
|
||||
:marked
|
||||
The router composes the appropriate two-part destination URL from this array:
|
||||
code-example(format="." language="bash").
|
||||
|
@ -560,12 +588,12 @@ code-example(format="." language="bash").
|
|||
|
||||
As usual, we write a constructor that asks Angular to inject that service among the other services
|
||||
that the component require and reference them as private variables.
|
||||
+makeExample('router/ts/app/heroes/hero-detail.component.ts','ctor')(format=".")
|
||||
+makeExample('router/ts/app/heroes/hero-detail.component.1.ts','ctor')(format=".")
|
||||
:marked
|
||||
Later, in the `ngOnInit` method,
|
||||
we ask the `RouteParams` service for the `id` parameter by name and
|
||||
tell the `HeroService` to fetch the hero with that `id`.
|
||||
+makeExample('router/ts/app/heroes/hero-detail.component.ts','ngOnInit')(format=".")
|
||||
+makeExample('router/ts/app/heroes/hero-detail.component.1.ts','ngOnInit')(format=".")
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
|
@ -582,7 +610,7 @@ code-example(format="." language="bash").
|
|||
The router `navigate` method takes the same one-item *link parameters array*
|
||||
that we wrote for the `[routerLink]` directive binding.
|
||||
It holds the **name of the `HeroListComponent` route**:
|
||||
+makeExample('router/ts/app/heroes/hero-detail.component.ts','gotoHeroes')(format=".")
|
||||
+makeExample('router/ts/app/heroes/hero-detail.component.1.ts','gotoHeroes')(format=".")
|
||||
:marked
|
||||
### Heroes App Wrap-up
|
||||
|
||||
|
@ -618,13 +646,11 @@ code-example(format="." language="bash").
|
|||
Here are the relevant files for this version of the sample application.
|
||||
+makeTabs(
|
||||
`router/ts/app/app.component.2.ts,
|
||||
router/ts/app/boot.2.ts,
|
||||
router/ts/app/heroes/hero-list.component.ts,
|
||||
router/ts/app/heroes/hero-detail.component.ts,
|
||||
router/ts/app/heroes/hero-list.component.1.ts,
|
||||
router/ts/app/heroes/hero-detail.component.1.ts,
|
||||
router/ts/app/heroes/hero.service.ts`,
|
||||
`,v2,,,`,
|
||||
null,
|
||||
`app.component.ts,
|
||||
boot.ts,
|
||||
hero-list.component.ts,
|
||||
hero-detail.component.ts,
|
||||
hero.service.ts`)
|
||||
|
@ -714,7 +740,7 @@ code-example(format="." language="bash").
|
|||
<a id="child-router"></id>
|
||||
### Child Routing Component
|
||||
We create a new `app/crisis-center` folder and add `crisis-center-component.ts` to it with the following contents:
|
||||
+makeExample('router/ts/app/crisis-center/crisis-center.component.ts', 'minus-imports', 'crisis-center/crisis-center.component.ts (minus imports)')
|
||||
+makeExample('router/ts/app/crisis-center/crisis-center.component.1.ts', 'minus-imports', 'crisis-center/crisis-center.component.ts (minus imports)')
|
||||
:marked
|
||||
The `CrisisCenterComponent` parallels the `AppComponent`.
|
||||
|
||||
|
@ -736,7 +762,7 @@ code-example(format="." language="bash").
|
|||
### Service isolation
|
||||
We add the `CrisisService` to the component's providers array
|
||||
instead of registering it with the `bootstrap` function in `boot.ts`.
|
||||
+makeExample('router/ts/app/crisis-center/crisis-center.component.ts', 'providers')
|
||||
+makeExample('router/ts/app/crisis-center/crisis-center.component.1.ts', 'providers')
|
||||
:marked
|
||||
This step limits the scope of that service to the *Crisis Center* component and its sub-component tree.
|
||||
No component outside of the *Crisis Center* needs access to the `CrisisService`.
|
||||
|
@ -748,7 +774,7 @@ code-example(format="." language="bash").
|
|||
|
||||
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=".")
|
||||
+makeExample('router/ts/app/crisis-center/crisis-center.component.1.ts', 'route-config', 'app/crisis-center/crisis-center.component.ts (routes only)' )(format=".")
|
||||
:marked
|
||||
There is an *important difference in the paths*. They both begin at `/`.
|
||||
Normally such paths would refer to the root of the application.
|
||||
|
@ -807,7 +833,7 @@ code-example(format="").
|
|||
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=".")
|
||||
+makeExample('router/ts/app/crisis-center/crisis-center.component.1.ts', 'default-route', 'app/crisis-center/crisis-center.component.ts (default route)')(format=".")
|
||||
:marked
|
||||
There is `useAsDefault: true` again. That tells the router to compose the final URL using the path from the default child route.
|
||||
Concatenate the base URL with `/crisis-center/` and `/`, remove extraneous slashes, and we get:
|
||||
|
@ -872,7 +898,7 @@ code-example(format="").
|
|||
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.ts', 'cancel-save', 'crisis-detail.component.ts (excerpt)')(format=".")
|
||||
+makeExample('router/ts/app/crisis-center/crisis-detail.component.1.ts', 'cancel-save', 'crisis-detail.component.ts (excerpt)')(format=".")
|
||||
: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.
|
||||
|
@ -899,7 +925,7 @@ code-example(format="").
|
|||
<a id="routerCanDeactivate"></a>
|
||||
:marked
|
||||
We execute the dialog inside the router's `routerCanDeactivate` lifecycle hook method.
|
||||
+makeExample('router/ts/app/crisis-center/crisis-detail.component.ts', 'routerCanDeactivate', 'crisis-detail.component.ts (excerpt)')
|
||||
+makeExample('router/ts/app/crisis-center/crisis-detail.component.1.ts', 'routerCanDeactivate', 'crisis-detail.component.ts (excerpt)')
|
||||
:marked
|
||||
Notice that the `routerCanDeactivate` method *can* return synchronously;
|
||||
it returns `true` immediately if there is no crisis or there are no pending changes.
|
||||
|
@ -912,8 +938,196 @@ code-example(format="").
|
|||
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).
|
||||
The relevant *Crisis Center* code for this milestone is
|
||||
|
||||
+makeTabs(
|
||||
`router/ts/app/crisis-center/crisis-center.component.ts,
|
||||
router/ts/app/crisis-center/crisis-list.component.1.ts,
|
||||
router/ts/app/crisis-center/crisis-detail.component.1.ts,
|
||||
router/ts/app/crisis-center/crisis.service.ts
|
||||
`,
|
||||
null,
|
||||
`crisis-center.component.ts,
|
||||
crisis-list.component.ts,
|
||||
crisis-detail.component.ts,
|
||||
crisis.service.ts,
|
||||
`)
|
||||
|
||||
|
||||
<a id="query-parameters"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
## Milestone #4: Query Parameters
|
||||
|
||||
We use [*route parameters*](#route-parameters) to specify a parameterized value *within* the route URL
|
||||
as we do when navigating to the `HeroDetailComponent` in order to view-and-edit the hero with *id:15*.
|
||||
code-example(format="." language="bash").
|
||||
localhost:3000/hero/15
|
||||
:marked
|
||||
Sometimes we 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`.
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/router/selected-hero.png' alt="Selected hero" width="175")
|
||||
:marked
|
||||
That becomes possible if we can include hero Magneta's `id` in the URL when we
|
||||
return from the `HeroDetailComponent`, a scenario we'll pursue in a moment.
|
||||
|
||||
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 —
|
||||
`before='1/1/2017' & after='12/31/2015'` — in a variety of formats — `during='currentYear'` .
|
||||
|
||||
These kinds of parameters don't fit easily in a URL *path*. Even if we could define a suitable URL token scheme,
|
||||
doing so greatly complicates the pattern matching required to translate an incoming URL to a named route.
|
||||
|
||||
The **URL query string** is the ideal vehicle for conveying arbitrarily complex information during navigation.
|
||||
The query string isn't involved in pattern matching and affords enormous flexiblity of expression.
|
||||
Almost anything serializable can appear in a query string.
|
||||
|
||||
The Component Router supports navigation with query strings as well as route parameters.
|
||||
We define query string parameters in the *route parameters object* just like we do with route parameters.
|
||||
|
||||
<a id="route-parameters-object"></a>
|
||||
### Route parameters object
|
||||
When navigating to the `HeroDetailComponent` we specified the `id` of the hero-to-edit in the
|
||||
*route parameters object* 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=".")
|
||||
: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/app.component.2.ts','hero-detail-route')(format=".")
|
||||
: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=".")
|
||||
:marked
|
||||
This array lacks a route parameters object because we had no reason to send information to the `HeroListComponent`.
|
||||
|
||||
Now we have a reason. We'd like to send the id of the current hero with the navigation request so that the
|
||||
`HeroListComponent` can highlight that hero in its list.
|
||||
|
||||
We do that with a route parameters object in the same manner as before.
|
||||
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=".")
|
||||
:marked
|
||||
The application still works. Clicking "back" returns to the hero list view.
|
||||
|
||||
Look at the browser address bar. It should look something like this:
|
||||
code-example(format="." language="bash").
|
||||
localhost:3000/heroes?id=15&foo=foo
|
||||
.l-sub-section
|
||||
:marked
|
||||
Unfortunately, the browser address bar does not change when running the live example in plunker.
|
||||
|
||||
You can take our word for it or
|
||||
download the sample from within plunker, unzip it, and browse to `index.html`.
|
||||
You may have to launch it in a local server such as
|
||||
[http-server](https://www.npmjs.com/package/http-server) or [lite-server](https://github.com/johnpapa/lite-server).
|
||||
:marked
|
||||
The `id` value appears in the query string (`?id=15&foo=foo`), not in the URL path.
|
||||
The path for the "Heroes" route doesn't have an `:id` token.
|
||||
|
||||
.alert.is-helpful
|
||||
:marked
|
||||
The router replaces route path tokens with corresponding values from the route parameters object.
|
||||
**Every parameter _not_ consumed by a route path goes in the query string.**
|
||||
:marked
|
||||
### Query parameters in the *RouteParams* service
|
||||
|
||||
The list of heroes is unchanged. No hero row is highlighted.
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
The [live example](/resources/live-examples/router/ts/plnkr.html) *does* highlight the selected
|
||||
row because it demonstrates the final state of the application which includes the steps we're *about* to cover.
|
||||
At the moment we're describing the state of affairs *prior* to those steps.
|
||||
:marked
|
||||
The `HeroListComponent` isn't expecting any parameters at all and wouldn't know what to do with them.
|
||||
Let's change that.
|
||||
|
||||
When navigating from the `HeroListComponent` to the `HeroDetailComponent`
|
||||
the router picked up the route parameter object and made it available to the `HeroDetailComponent`
|
||||
in the `RouteParams` service. We injected that service in the constructor of the `HeroDetailComponent`.
|
||||
|
||||
This time we'll be navigating in the opposite direction, from the `HeroDetailComponent` to the `HeroListComponent`.
|
||||
This time we'll inject the `RouteParams` service in the constructor of the `HeroListComponent`.
|
||||
|
||||
First we extend the router import statement to include the `RouteParams` service symbol;
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.ts','import-route-params', 'hero-list.component.ts (import)')(format=".")
|
||||
:marked
|
||||
Then we extend the constructor to inject the `RouteParams` service and extract the `id` parameter as the `_selectedId`:
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.ts','ctor', 'hero-list.component.ts (constructor)')(format=".")
|
||||
.l-sub-section
|
||||
:marked
|
||||
All route parameters are strings.
|
||||
The (+) in front of the `routeParameters.get` 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=".")
|
||||
:marked
|
||||
We update our template with a [Class Binding](template-syntax.html#class-binding) to that `isSelected` method.
|
||||
The binding adds the `selected` CSS class when the method returns `true` and removes it when `false`.
|
||||
Look for it within the repeated `<li>` tag as shown here:
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.ts','template', 'hero-list.component.ts (template)')(format=".")
|
||||
:marked
|
||||
Finally, we add the `selected` class to our CSS styles
|
||||
+makeExample('router/ts/styles.css','selected', 'styles.css (selected class)')(format=".")
|
||||
:marked
|
||||
When the user navigates from the heroes list to the "Magneta" hero and back, "Magneta" appears selected:
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/router/selected-hero.png' alt="Selected List" width="250")
|
||||
:marked
|
||||
The `foo` query string parameter is harmless and continues to be ignored.
|
||||
|
||||
### Child Routers and Query Parameters
|
||||
|
||||
We can define query parameters for child routers too.
|
||||
|
||||
The technique is precisely the same.
|
||||
In fact, we made exactly the same changes to the *Crisis Center* feature.
|
||||
Confirm the similarities in these *Hero* and *CrisisCenter* components,
|
||||
arranged side-by-side for easy comparison:
|
||||
+makeTabs(
|
||||
`router/ts/app/heroes/hero-list.component.ts,
|
||||
router/ts/app/crisis-center/crisis-list.component.ts,
|
||||
router/ts/app/heroes/hero-detail.component.ts,
|
||||
router/ts/app/crisis-center/crisis-detail.component.ts
|
||||
`,
|
||||
null,
|
||||
`hero-list.component.ts,
|
||||
crisis-list.component.ts,
|
||||
hero-detail.component.ts,
|
||||
crisis-detail.component.ts
|
||||
`)
|
||||
:marked
|
||||
When we navigate back from a `CrisisDetailComponent` that is showing the *Asteroid* crisis,
|
||||
we see that crisis properly selected in the list like this:
|
||||
figure.image-display
|
||||
img(src='/resources/images/devguide/router/selected-crisis.png' alt="Selected crisis" )
|
||||
:marked
|
||||
**Look at the browser address bar again**. It's *different*. It looks something like this:
|
||||
code-example(format="." language="bash").
|
||||
localhost:3000/crisis-center/;id=3;foo=foo
|
||||
:marked
|
||||
The query string parameters are no longer 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
|
||||
in a [1996 proposal](http://www.w3.org/DesignIssues/MatrixURIs.html) by the founder of the web, Tim Berners-Lee.
|
||||
|
||||
Although matrix notation never made it into the HTML standard, it is legal and
|
||||
it became popular among browser routing systems as a way to isolate parameters
|
||||
belonging to parent and child routes. The Angular Component Router is such a system.
|
||||
|
||||
The syntax may seem strange to us but users are unlikely to notice or care
|
||||
as long as the URL can be emailed and pasted into a browser address bar
|
||||
as this one can.
|
||||
|
||||
|
||||
<a id="final-app"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
|
@ -1016,7 +1230,7 @@ code-example(format="").
|
|||
|
||||
|
||||
.l-main-section
|
||||
<a id="link-parameter-array"></a>
|
||||
<a id="link-parameters-array"></a>
|
||||
:marked
|
||||
## Link Parameters Array
|
||||
We've mentioned the *Link Parameters Array* several times. We've used it several times.
|
||||
|
@ -1025,7 +1239,7 @@ code-example(format="").
|
|||
+makeExample('router/ts/app/app.component.3.ts', 'h-anchor')(format=".")
|
||||
:marked
|
||||
We've written a two element array when specifying a route parameter like this
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.ts', 'nav-to-detail')(format=".")
|
||||
+makeExample('router/ts/app/heroes/hero-list.component.1.ts', 'nav-to-detail')(format=".")
|
||||
:marked
|
||||
These two 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.
|
||||
|
@ -1121,15 +1335,9 @@ code-example(format="").
|
|||
|
||||
When the router navigates to a new component view, it updates the browser's location and history
|
||||
with a URL for that view.
|
||||
This is a strictly local URL. The browser shouldn't send a request to the server
|
||||
This is a strictly local URL. The browser shouldn't send this URL to the server
|
||||
and should not reload the page.
|
||||
.l-sub-section
|
||||
:marked
|
||||
We're talking now about the ***browser*** URL
|
||||
**not** the *route* URL that we record in a `RouteDefinition`.
|
||||
The browser URL is what we paste into the browser's **address bar**
|
||||
and email to folks so they can deep-link into an application page.
|
||||
:marked
|
||||
|
||||
Modern HTML 5 browsers support
|
||||
[history.pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries),
|
||||
a technique that changes a browser's location and history without triggering a server page request.
|
||||
|
@ -1142,13 +1350,18 @@ code-example(format=".", language="bash").
|
|||
:marked
|
||||
Older browsers send page requests to the server when the location URL changes ...
|
||||
unless the change occurs after a "#" (called the "hash").
|
||||
Routers take advantage of this exception by composing in-application route
|
||||
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 Angular Component Router supports both styles.
|
||||
We set our preference by providing a `LocationStrategy` during the bootstrapping process.
|
||||
The Angular Component Router supports both styles with two `LocationStrategy` providers:
|
||||
1. `PathLocationStrategy` - the default "HTML 5 pushState" style.
|
||||
1. `HashLocationStrategy` - the "hash URL" style.
|
||||
|
||||
The router's `ROUTER_PROVIDERS` array sets the `LocationStrategy` to the `PathLocationStrategy`,
|
||||
making it the default strategy.
|
||||
We can switch to the `HashLocationStrategy` with an override during the bootstrapping process if we prefer it.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn about "providers" and the bootstrap process in the
|
||||
|
@ -1176,11 +1389,10 @@ code-example(format=".", language="bash").
|
|||
resort to hash routes.
|
||||
|
||||
### HTML 5 URLs and the *<base href>*
|
||||
The router use the "[HTML 5 pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries)"
|
||||
style by default.
|
||||
We don't have to provide the router's `PathLocationStrategy` because it's loaded automatically.
|
||||
|
||||
We *must* add a
|
||||
While the router use the "[HTML 5 pushState](https://developer.mozilla.org/en-US/docs/Web/API/History_API#Adding_and_modifying_history_entries)"
|
||||
style by default, we *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 `<head>` of the `index.html`.
|
||||
+makeExample('router/ts/index.html','base-href')(format=".")
|
||||
|
@ -1200,14 +1412,16 @@ code-example(format=".", language="bash").
|
|||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Learn about the [APP_BASE_HREF](https://angular.io/docs/ts/latest/api/router/APP_BASE_HREF-const.html)
|
||||
Learn about the [APP_BASE_HREF](https://angular.io/docs/ts/latest/api/router/APP_BASE_HREF-let.html)
|
||||
in the API Guide.
|
||||
:marked
|
||||
### *HashLocationStrategy*
|
||||
We can go old-school with the `HashLocationStrategy` by
|
||||
providing it as the router's `LocationStrategy` during application bootstrapping.
|
||||
|
||||
That means importing `provide` for Dependency Injection and the
|
||||
`Location` and `HashLocationStrategy` symbols from the router,
|
||||
then providing that strategy in the call to `bootstrap`:
|
||||
+makeExample('router/ts/app/boot.2.ts', 'hash-strategy')
|
||||
First, import the `provide` symbol for Dependency Injection and the
|
||||
`Location` and `HashLocationStrategy` symbols from the router.
|
||||
|
||||
Then *override* the default strategy defined in `ROUTE_PROVIDERS` by
|
||||
providing the `HashLocationStrategy` later in the `bootstrap` providers array argument:
|
||||
+makeExample('router/ts/app/boot.2.ts','', 'boot.ts (hash URL strategy)')
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Loading…
Reference in New Issue