docs(aio): Updated usage of Observables in router guide. Added section for advanced redirects (#18197)
PR Close #18197
This commit is contained in:
parent
fa6b802be4
commit
53be85a7fb
|
@ -12,13 +12,13 @@ describe('Router', () => {
|
||||||
beforeAll(() => browser.get(''));
|
beforeAll(() => browser.get(''));
|
||||||
|
|
||||||
function getPageStruct() {
|
function getPageStruct() {
|
||||||
const hrefEles = element.all(by.css('my-app a'));
|
const hrefEles = element.all(by.css('my-app > nav a'));
|
||||||
const crisisDetail = element.all(by.css('my-app > ng-component > ng-component > ng-component > div')).first();
|
const crisisDetail = element.all(by.css('my-app > ng-component > ng-component > ng-component > div')).first();
|
||||||
const heroDetail = element(by.css('my-app > ng-component > div'));
|
const heroDetail = element(by.css('my-app > ng-component > div'));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hrefs: hrefEles,
|
hrefs: hrefEles,
|
||||||
activeHref: element(by.css('my-app a.active')),
|
activeHref: element(by.css('my-app > nav a.active')),
|
||||||
|
|
||||||
crisisHref: hrefEles.get(0),
|
crisisHref: hrefEles.get(0),
|
||||||
crisisList: element.all(by.css('my-app > ng-component > ng-component li')),
|
crisisList: element.all(by.css('my-app > ng-component > ng-component li')),
|
||||||
|
|
|
@ -15,6 +15,10 @@
|
||||||
height: 1.6em;
|
height: 1.6em;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
.items li a {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
.items li:hover {
|
.items li:hover {
|
||||||
color: #607D8B;
|
color: #607D8B;
|
||||||
background-color: #DDD;
|
background-color: #DDD;
|
||||||
|
|
|
@ -28,7 +28,7 @@ const appRoutes: Routes = [
|
||||||
data: { preload: true }
|
data: { preload: true }
|
||||||
},
|
},
|
||||||
// #enddocregion preload-v2
|
// #enddocregion preload-v2
|
||||||
{ path: '', redirectTo: '/heroes', pathMatch: 'full' },
|
{ path: '', redirectTo: '/superheroes', pathMatch: 'full' },
|
||||||
{ path: '**', component: PageNotFoundComponent }
|
{ path: '**', component: PageNotFoundComponent }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
// #docplaster
|
||||||
|
// #docregion
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-app',
|
||||||
|
// #docregion template
|
||||||
|
template: `
|
||||||
|
<h1 class="title">Angular Router</h1>
|
||||||
|
<nav>
|
||||||
|
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
|
||||||
|
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
|
||||||
|
<a routerLink="/admin" routerLinkActive="active">Admin</a>
|
||||||
|
<a routerLink="/login" routerLinkActive="active">Login</a>
|
||||||
|
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
|
||||||
|
</nav>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
<router-outlet name="popup"></router-outlet>
|
||||||
|
`
|
||||||
|
// #enddocregion template
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import { Component } from '@angular/core';
|
||||||
<h1 class="title">Angular Router</h1>
|
<h1 class="title">Angular Router</h1>
|
||||||
<nav>
|
<nav>
|
||||||
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
|
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
|
||||||
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
|
<a routerLink="/superheroes" routerLinkActive="active">Heroes</a>
|
||||||
<a routerLink="/admin" routerLinkActive="active">Admin</a>
|
<a routerLink="/admin" routerLinkActive="active">Admin</a>
|
||||||
<a routerLink="/login" routerLinkActive="active">Login</a>
|
<a routerLink="/login" routerLinkActive="active">Login</a>
|
||||||
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
|
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// #docregion
|
// #docregion
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { CanDeactivate,
|
import { CanDeactivate,
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
RouterStateSnapshot } from '@angular/router';
|
RouterStateSnapshot } from '@angular/router';
|
||||||
|
@ -13,7 +14,7 @@ export class CanDeactivateGuard implements CanDeactivate<CrisisDetailComponent>
|
||||||
component: CrisisDetailComponent,
|
component: CrisisDetailComponent,
|
||||||
route: ActivatedRouteSnapshot,
|
route: ActivatedRouteSnapshot,
|
||||||
state: RouterStateSnapshot
|
state: RouterStateSnapshot
|
||||||
): Promise<boolean> | boolean {
|
): Observable<boolean> | boolean {
|
||||||
// Get the Crisis Center ID
|
// Get the Crisis Center ID
|
||||||
console.log(route.paramMap.get('id'));
|
console.log(route.paramMap.get('id'));
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ export class CanDeactivateGuard implements CanDeactivate<CrisisDetailComponent>
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Otherwise ask the user with the dialog service and return its
|
// Otherwise ask the user with the dialog service and return its
|
||||||
// promise which resolves to true or false when the user decides
|
// observable which resolves to true or false when the user decides
|
||||||
return component.dialogService.confirm('Discard changes?');
|
return component.dialogService.confirm('Discard changes?');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
// #docregion
|
// #docregion
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
|
import 'rxjs/add/operator/take';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { Router, Resolve, RouterStateSnapshot,
|
import { Router, Resolve, RouterStateSnapshot,
|
||||||
ActivatedRouteSnapshot } from '@angular/router';
|
ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
|
||||||
import { Crisis, CrisisService } from './crisis.service';
|
import { Crisis, CrisisService } from './crisis.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CrisisDetailResolver implements Resolve<Crisis> {
|
export class CrisisDetailResolver implements Resolve<Crisis> {
|
||||||
constructor(private cs: CrisisService, private router: Router) {}
|
constructor(private cs: CrisisService, private router: Router) {}
|
||||||
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Crisis> {
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Crisis> {
|
||||||
let id = route.paramMap.get('id');
|
let id = route.paramMap.get('id');
|
||||||
|
|
||||||
return this.cs.getCrisis(id).then(crisis => {
|
return this.cs.getCrisis(id).take(1).map(crisis => {
|
||||||
if (crisis) {
|
if (crisis) {
|
||||||
return crisis;
|
return crisis;
|
||||||
} else { // id not found
|
} else { // id not found
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
// #docplaster
|
// #docplaster
|
||||||
// #docregion
|
// #docregion
|
||||||
import 'rxjs/add/operator/switchMap';
|
import 'rxjs/add/operator/switchMap';
|
||||||
import { Component, OnInit, HostBinding } from '@angular/core';
|
import { Component, OnInit, HostBinding } from '@angular/core';
|
||||||
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
|
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
import { slideInDownAnimation } from '../animations';
|
import { slideInDownAnimation } from '../animations';
|
||||||
import { Crisis, CrisisService } from './crisis.service';
|
import { Crisis, CrisisService } from './crisis.service';
|
||||||
|
@ -45,7 +46,8 @@ export class CrisisDetailComponent implements OnInit {
|
||||||
// #docregion ngOnInit
|
// #docregion ngOnInit
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.route.paramMap
|
this.route.paramMap
|
||||||
.switchMap((params: ParamMap) => this.service.getCrisis(params.get('id')))
|
.switchMap((params: ParamMap) =>
|
||||||
|
this.service.getCrisis(params.get('id')))
|
||||||
.subscribe((crisis: Crisis) => {
|
.subscribe((crisis: Crisis) => {
|
||||||
if (crisis) {
|
if (crisis) {
|
||||||
this.editName = crisis.name;
|
this.editName = crisis.name;
|
||||||
|
@ -66,13 +68,13 @@ export class CrisisDetailComponent implements OnInit {
|
||||||
this.gotoCrises();
|
this.gotoCrises();
|
||||||
}
|
}
|
||||||
|
|
||||||
canDeactivate(): Promise<boolean> | boolean {
|
canDeactivate(): Observable<boolean> | boolean {
|
||||||
// Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged
|
// Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged
|
||||||
if (!this.crisis || this.crisis.name === this.editName) {
|
if (!this.crisis || this.crisis.name === this.editName) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Otherwise ask the user with the dialog service and return its
|
// Otherwise ask the user with the dialog service and return its
|
||||||
// promise which resolves to true or false when the user decides
|
// observable which resolves to true or false when the user decides
|
||||||
return this.dialogService.confirm('Discard changes?');
|
return this.dialogService.confirm('Discard changes?');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// #docregion
|
// #docregion
|
||||||
import { Component, OnInit, HostBinding } from '@angular/core';
|
import { Component, OnInit, HostBinding } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
import { slideInDownAnimation } from '../animations';
|
import { slideInDownAnimation } from '../animations';
|
||||||
import { Crisis } from './crisis.service';
|
import { Crisis } from './crisis.service';
|
||||||
|
@ -62,13 +63,13 @@ export class CrisisDetailComponent implements OnInit {
|
||||||
// #enddocregion cancel-save
|
// #enddocregion cancel-save
|
||||||
|
|
||||||
// #docregion canDeactivate
|
// #docregion canDeactivate
|
||||||
canDeactivate(): Promise<boolean> | boolean {
|
canDeactivate(): Observable<boolean> | boolean {
|
||||||
// Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged
|
// Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged
|
||||||
if (!this.crisis || this.crisis.name === this.editName) {
|
if (!this.crisis || this.crisis.name === this.editName) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Otherwise ask the user with the dialog service and return its
|
// Otherwise ask the user with the dialog service and return its
|
||||||
// promise which resolves to true or false when the user decides
|
// observable which resolves to true or false when the user decides
|
||||||
return this.dialogService.confirm('Discard changes?');
|
return this.dialogService.confirm('Discard changes?');
|
||||||
}
|
}
|
||||||
// #enddocregion canDeactivate
|
// #enddocregion canDeactivate
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'rxjs/add/operator/do';
|
|
||||||
import 'rxjs/add/operator/switchMap';
|
import 'rxjs/add/operator/switchMap';
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
|
|
||||||
import { Crisis, CrisisService } from './crisis.service';
|
import { Crisis, CrisisService } from './crisis.service';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
@ -10,35 +10,34 @@ import { Observable } from 'rxjs/Observable';
|
||||||
// #docregion relative-navigation-router-link
|
// #docregion relative-navigation-router-link
|
||||||
template: `
|
template: `
|
||||||
<ul class="items">
|
<ul class="items">
|
||||||
<li *ngFor="let crisis of crises | async">
|
<li *ngFor="let crisis of crises$ | async"
|
||||||
<a [routerLink]="[crisis.id]"
|
[class.selected]="crisis.id === selectedId">
|
||||||
[class.selected]="isSelected(crisis)">
|
<a [routerLink]="[crisis.id]">
|
||||||
<span class="badge">{{ crisis.id }}</span>
|
<span class="badge">{{ crisis.id }}</span>{{ crisis.name }}
|
||||||
{{ crisis.name }}
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>`
|
</ul>
|
||||||
|
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
`
|
||||||
// #enddocregion relative-navigation-router-link
|
// #enddocregion relative-navigation-router-link
|
||||||
})
|
})
|
||||||
export class CrisisListComponent implements OnInit {
|
export class CrisisListComponent implements OnInit {
|
||||||
crises: Observable<Crisis[]>;
|
crises$: Observable<Crisis[]>;
|
||||||
selectedId: number;
|
selectedId: number;
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private service: CrisisService,
|
private service: CrisisService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute
|
||||||
private router: Router
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.crises = this.route.paramMap
|
this.crises$ = this.route.paramMap
|
||||||
.switchMap((params: ParamMap) => {
|
.switchMap((params: ParamMap) => {
|
||||||
this.selectedId = +params.get('id');
|
this.selectedId = +params.get('id');
|
||||||
return this.service.getCrises();
|
return this.service.getCrises();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isSelected(crisis: Crisis) {
|
|
||||||
return crisis.id === this.selectedId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
// #docregion
|
// #docregion
|
||||||
import 'rxjs/add/operator/switchMap';
|
import 'rxjs/add/operator/switchMap';
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
|
|
||||||
import { Crisis, CrisisService } from './crisis.service';
|
import { Crisis, CrisisService } from './crisis.service';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
<ul class="items">
|
<ul class="items">
|
||||||
<li *ngFor="let crisis of crises | async"
|
<li *ngFor="let crisis of crises$ | async"
|
||||||
(click)="onSelect(crisis)"
|
[class.selected]="crisis.id === selectedId">
|
||||||
[class.selected]="isSelected(crisis)">
|
<a [routerLink]="[crisis.id]">
|
||||||
<span class="badge">{{ crisis.id }}</span>
|
<span class="badge">{{ crisis.id }}</span>{{ crisis.name }}
|
||||||
{{ crisis.name }}
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -22,35 +21,21 @@ import { Crisis, CrisisService } from './crisis.service';
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
export class CrisisListComponent implements OnInit {
|
export class CrisisListComponent implements OnInit {
|
||||||
crises: Observable<Crisis[]>;
|
crises$: Observable<Crisis[]>;
|
||||||
selectedId: number;
|
selectedId: number;
|
||||||
|
|
||||||
// #docregion ctor
|
// #docregion ctor
|
||||||
constructor(
|
constructor(
|
||||||
private service: CrisisService,
|
private service: CrisisService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute
|
||||||
private router: Router
|
|
||||||
) {}
|
) {}
|
||||||
// #enddocregion ctor
|
// #enddocregion ctor
|
||||||
|
|
||||||
isSelected(crisis: Crisis) {
|
|
||||||
return crisis.id === this.selectedId;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.crises = this.route.paramMap
|
this.crises$ = this.route.paramMap
|
||||||
.switchMap((params: ParamMap) => {
|
.switchMap((params: ParamMap) => {
|
||||||
this.selectedId = +params.get('id');
|
this.selectedId = +params.get('id');
|
||||||
return this.service.getCrises();
|
return this.service.getCrises();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// #docregion onSelect
|
|
||||||
onSelect(crisis: Crisis) {
|
|
||||||
this.selectedId = crisis.id;
|
|
||||||
|
|
||||||
// Navigate with relative link
|
|
||||||
this.router.navigate([crisis.id], { relativeTo: this.route });
|
|
||||||
}
|
|
||||||
// #enddocregion onSelect
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
// #docplaster
|
// #docplaster
|
||||||
// #docregion , mock-crises
|
// #docregion , mock-crises
|
||||||
|
import 'rxjs/add/observable/of';
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
|
|
||||||
export class Crisis {
|
export class Crisis {
|
||||||
constructor(public id: number, public name: string) { }
|
constructor(public id: number, public name: string) { }
|
||||||
}
|
}
|
||||||
|
@ -12,20 +16,18 @@ const CRISES = [
|
||||||
];
|
];
|
||||||
// #enddocregion mock-crises
|
// #enddocregion mock-crises
|
||||||
|
|
||||||
let crisesPromise = Promise.resolve(CRISES);
|
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CrisisService {
|
export class CrisisService {
|
||||||
|
|
||||||
static nextCrisisId = 100;
|
static nextCrisisId = 100;
|
||||||
|
private crises$: BehaviorSubject<Crisis[]> = new BehaviorSubject<Crisis[]>(CRISES);
|
||||||
|
|
||||||
getCrises() { return crisesPromise; }
|
getCrises() { return this.crises$; }
|
||||||
|
|
||||||
getCrisis(id: number | string) {
|
getCrisis(id: number | string) {
|
||||||
return crisesPromise
|
return this.getCrises()
|
||||||
.then(crises => crises.find(crisis => crisis.id === +id));
|
.map(crises => crises.find(crisis => crisis.id === +id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
@ -33,7 +35,8 @@ export class CrisisService {
|
||||||
name = name.trim();
|
name = name.trim();
|
||||||
if (name) {
|
if (name) {
|
||||||
let crisis = new Crisis(CrisisService.nextCrisisId++, name);
|
let crisis = new Crisis(CrisisService.nextCrisisId++, name);
|
||||||
crisesPromise.then(crises => crises.push(crisis));
|
CRISES.push(crisis);
|
||||||
|
this.crises$.next(CRISES);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// #docregion
|
// #docregion
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
// #docregion
|
// #docregion
|
||||||
|
import 'rxjs/add/observable/of';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Async modal dialog service
|
* Async modal dialog service
|
||||||
* DialogService makes this app easier to test by faking this service.
|
* DialogService makes this app easier to test by faking this service.
|
||||||
|
@ -9,11 +12,11 @@ import { Injectable } from '@angular/core';
|
||||||
export class DialogService {
|
export class DialogService {
|
||||||
/**
|
/**
|
||||||
* Ask user to confirm an action. `message` explains the action and choices.
|
* Ask user to confirm an action. `message` explains the action and choices.
|
||||||
* Returns promise resolving to `true`=confirm or `false`=cancel
|
* Returns observable resolving to `true`=confirm or `false`=cancel
|
||||||
*/
|
*/
|
||||||
confirm(message?: string) {
|
confirm(message?: string): Observable<boolean> {
|
||||||
return new Promise<boolean>(resolve => {
|
const confirmation = window.confirm(message || 'Is it OK?');
|
||||||
return resolve(window.confirm(message || 'Is it OK?'));
|
|
||||||
});
|
return Observable.of(confirmation);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import 'rxjs/add/operator/switchMap';
|
import 'rxjs/add/operator/switchMap';
|
||||||
// #enddocregion rxjs-operator-import
|
// #enddocregion rxjs-operator-import
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
// #docregion imports
|
// #docregion imports
|
||||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
// #enddocregion imports
|
// #enddocregion imports
|
||||||
|
@ -13,7 +14,7 @@ import { Hero, HeroService } from './hero.service';
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
<h2>HEROES</h2>
|
<h2>HEROES</h2>
|
||||||
<div *ngIf="hero">
|
<div *ngIf="hero$ | async as hero">
|
||||||
<h3>"{{ hero.name }}"</h3>
|
<h3>"{{ hero.name }}"</h3>
|
||||||
<div>
|
<div>
|
||||||
<label>Id: </label>{{ hero.id }}</div>
|
<label>Id: </label>{{ hero.id }}</div>
|
||||||
|
@ -28,7 +29,7 @@ import { Hero, HeroService } from './hero.service';
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
export class HeroDetailComponent implements OnInit {
|
export class HeroDetailComponent implements OnInit {
|
||||||
hero: Hero;
|
hero$: Observable<Hero>;
|
||||||
|
|
||||||
// #docregion ctor
|
// #docregion ctor
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -40,10 +41,9 @@ export class HeroDetailComponent implements OnInit {
|
||||||
|
|
||||||
// #docregion ngOnInit
|
// #docregion ngOnInit
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.route.paramMap
|
this.hero$ = this.route.paramMap
|
||||||
.switchMap((params: ParamMap) =>
|
.switchMap((params: ParamMap) =>
|
||||||
this.service.getHero(params.get('id')))
|
this.service.getHero(params.get('id')));
|
||||||
.subscribe((hero: Hero) => this.hero = hero);
|
|
||||||
}
|
}
|
||||||
// #enddocregion ngOnInit
|
// #enddocregion ngOnInit
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,14 @@
|
||||||
// #docregion
|
// #docregion
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
import { Hero, HeroService } from './hero.service';
|
import { Hero, HeroService } from './hero.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
<h2>HEROES</h2>
|
<h2>HEROES</h2>
|
||||||
<div *ngIf="hero">
|
<div *ngIf="hero$ | async as hero">
|
||||||
<h3>"{{ hero.name }}"</h3>
|
<h3>"{{ hero.name }}"</h3>
|
||||||
<div>
|
<div>
|
||||||
<label>Id: </label>{{ hero.id }}</div>
|
<label>Id: </label>{{ hero.id }}</div>
|
||||||
|
@ -23,7 +24,7 @@ import { Hero, HeroService } from './hero.service';
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
export class HeroDetailComponent implements OnInit {
|
export class HeroDetailComponent implements OnInit {
|
||||||
hero: Hero;
|
hero$: Observable<Hero>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -35,8 +36,7 @@ export class HeroDetailComponent implements OnInit {
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
let id = this.route.snapshot.paramMap.get('id');
|
let id = this.route.snapshot.paramMap.get('id');
|
||||||
|
|
||||||
this.service.getHero(id)
|
this.hero$ = this.service.getHero(id);
|
||||||
.then((hero: Hero) => this.hero = hero);
|
|
||||||
}
|
}
|
||||||
// #enddocregion snapshot
|
// #enddocregion snapshot
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import 'rxjs/add/operator/switchMap';
|
import 'rxjs/add/operator/switchMap';
|
||||||
// #enddocregion rxjs-operator-import
|
// #enddocregion rxjs-operator-import
|
||||||
import { Component, OnInit, HostBinding } from '@angular/core';
|
import { Component, OnInit, HostBinding } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
|
|
||||||
import { slideInDownAnimation } from '../animations';
|
import { slideInDownAnimation } from '../animations';
|
||||||
|
@ -13,7 +14,7 @@ import { Hero, HeroService } from './hero.service';
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
<h2>HEROES</h2>
|
<h2>HEROES</h2>
|
||||||
<div *ngIf="hero">
|
<div *ngIf="hero$ | async as hero">
|
||||||
<h3>"{{ hero.name }}"</h3>
|
<h3>"{{ hero.name }}"</h3>
|
||||||
<div>
|
<div>
|
||||||
<label>Id: </label>{{ hero.id }}</div>
|
<label>Id: </label>{{ hero.id }}</div>
|
||||||
|
@ -22,7 +23,7 @@ import { Hero, HeroService } from './hero.service';
|
||||||
<input [(ngModel)]="hero.name" placeholder="name"/>
|
<input [(ngModel)]="hero.name" placeholder="name"/>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
<button (click)="gotoHeroes()">Back</button>
|
<button (click)="gotoHeroes(hero)">Back</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
@ -35,7 +36,7 @@ export class HeroDetailComponent implements OnInit {
|
||||||
@HostBinding('style.position') position = 'absolute';
|
@HostBinding('style.position') position = 'absolute';
|
||||||
// #enddocregion host-bindings
|
// #enddocregion host-bindings
|
||||||
|
|
||||||
hero: Hero;
|
hero$: Observable<Hero>;
|
||||||
|
|
||||||
// #docregion ctor
|
// #docregion ctor
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -47,16 +48,15 @@ export class HeroDetailComponent implements OnInit {
|
||||||
|
|
||||||
// #docregion ngOnInit
|
// #docregion ngOnInit
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.route.paramMap
|
this.hero$ = this.route.paramMap
|
||||||
.switchMap((params: ParamMap) =>
|
.switchMap((params: ParamMap) =>
|
||||||
this.service.getHero(params.get('id')))
|
this.service.getHero(params.get('id')));
|
||||||
.subscribe((hero: Hero) => this.hero = hero);
|
|
||||||
}
|
}
|
||||||
// #enddocregion ngOnInit
|
// #enddocregion ngOnInit
|
||||||
|
|
||||||
// #docregion gotoHeroes
|
// #docregion gotoHeroes
|
||||||
gotoHeroes() {
|
gotoHeroes(hero: Hero) {
|
||||||
let heroId = this.hero ? this.hero.id : null;
|
let heroId = hero ? hero.id : null;
|
||||||
// Pass along the hero id if available
|
// Pass along the hero id if available
|
||||||
// so that the HeroList component can select that hero.
|
// so that the HeroList component can select that hero.
|
||||||
// Include a junk 'foo' property for fun.
|
// Include a junk 'foo' property for fun.
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// TODO SOMEDAY: Feature Componetized like HeroCenter
|
// TODO SOMEDAY: Feature Componetized like HeroCenter
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
import { Hero, HeroService } from './hero.service';
|
import { Hero, HeroService } from './hero.service';
|
||||||
|
|
||||||
|
@ -11,9 +12,12 @@ import { Hero, HeroService } from './hero.service';
|
||||||
template: `
|
template: `
|
||||||
<h2>HEROES</h2>
|
<h2>HEROES</h2>
|
||||||
<ul class="items">
|
<ul class="items">
|
||||||
<li *ngFor="let hero of heroes | async"
|
<li *ngFor="let hero of heroes$ | async">
|
||||||
(click)="onSelect(hero)">
|
// #docregion nav-to-detail
|
||||||
<span class="badge">{{ hero.id }}</span> {{ hero.name }}
|
<a [routerLink]="['/hero', hero.id]">
|
||||||
|
<span class="badge">{{ hero.id }}</span>{{ hero.name }}
|
||||||
|
</a>
|
||||||
|
// #enddocregion nav-to-detail
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -22,7 +26,7 @@ import { Hero, HeroService } from './hero.service';
|
||||||
// #enddocregion template
|
// #enddocregion template
|
||||||
})
|
})
|
||||||
export class HeroListComponent implements OnInit {
|
export class HeroListComponent implements OnInit {
|
||||||
heroes: Promise<Hero[]>;
|
heroes$: Observable<Hero[]>;
|
||||||
|
|
||||||
// #docregion ctor
|
// #docregion ctor
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -32,16 +36,8 @@ export class HeroListComponent implements OnInit {
|
||||||
// #enddocregion ctor
|
// #enddocregion ctor
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.heroes = this.service.getHeroes();
|
this.heroes$ = this.service.getHeroes();
|
||||||
}
|
}
|
||||||
|
|
||||||
// #docregion select
|
|
||||||
onSelect(hero: Hero) {
|
|
||||||
// #docregion nav-to-detail
|
|
||||||
this.router.navigate(['/hero', hero.id]);
|
|
||||||
// #enddocregion nav-to-detail
|
|
||||||
}
|
|
||||||
// #enddocregion select
|
|
||||||
}
|
}
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { Observable } from 'rxjs/Observable';
|
||||||
// #enddocregion rxjs-imports
|
// #enddocregion rxjs-imports
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
// #docregion import-router
|
// #docregion import-router
|
||||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
// #enddocregion import-router
|
// #enddocregion import-router
|
||||||
|
|
||||||
import { Hero, HeroService } from './hero.service';
|
import { Hero, HeroService } from './hero.service';
|
||||||
|
@ -17,10 +17,11 @@ import { Hero, HeroService } from './hero.service';
|
||||||
template: `
|
template: `
|
||||||
<h2>HEROES</h2>
|
<h2>HEROES</h2>
|
||||||
<ul class="items">
|
<ul class="items">
|
||||||
<li *ngFor="let hero of heroes | async"
|
<li *ngFor="let hero of heroes$ | async"
|
||||||
[class.selected]="isSelected(hero)"
|
[class.selected]="hero.id === selectedId">
|
||||||
(click)="onSelect(hero)">
|
<a [routerLink]="['/hero', hero.id]">
|
||||||
<span class="badge">{{ hero.id }}</span> {{ hero.name }}
|
<span class="badge">{{ hero.id }}</span>{{ hero.name }}
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -30,18 +31,17 @@ import { Hero, HeroService } from './hero.service';
|
||||||
})
|
})
|
||||||
// #docregion ctor
|
// #docregion ctor
|
||||||
export class HeroListComponent implements OnInit {
|
export class HeroListComponent implements OnInit {
|
||||||
heroes: Observable<Hero[]>;
|
heroes$: Observable<Hero[]>;
|
||||||
|
|
||||||
private selectedId: number;
|
private selectedId: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private service: HeroService,
|
private service: HeroService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute
|
||||||
private router: Router
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.heroes = this.route.paramMap
|
this.heroes$ = this.route.paramMap
|
||||||
.switchMap((params: ParamMap) => {
|
.switchMap((params: ParamMap) => {
|
||||||
// (+) before `params.get()` turns the string into a number
|
// (+) before `params.get()` turns the string into a number
|
||||||
this.selectedId = +params.get('id');
|
this.selectedId = +params.get('id');
|
||||||
|
@ -49,16 +49,6 @@ export class HeroListComponent implements OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// #enddocregion ctor
|
// #enddocregion ctor
|
||||||
|
|
||||||
// #docregion isSelected
|
|
||||||
isSelected(hero: Hero) { return hero.id === this.selectedId; }
|
|
||||||
// #enddocregion isSelected
|
|
||||||
|
|
||||||
// #docregion select
|
|
||||||
onSelect(hero: Hero) {
|
|
||||||
this.router.navigate(['/hero', hero.id]);
|
|
||||||
}
|
|
||||||
// #enddocregion select
|
|
||||||
// #docregion ctor
|
// #docregion ctor
|
||||||
}
|
}
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
// #docregion
|
// #docregion
|
||||||
|
import 'rxjs/add/observable/of';
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
export class Hero {
|
export class Hero {
|
||||||
constructor(public id: number, public name: string) { }
|
constructor(public id: number, public name: string) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
let HEROES = [
|
const HEROES = [
|
||||||
new Hero(11, 'Mr. Nice'),
|
new Hero(11, 'Mr. Nice'),
|
||||||
new Hero(12, 'Narco'),
|
new Hero(12, 'Narco'),
|
||||||
new Hero(13, 'Bombasto'),
|
new Hero(13, 'Bombasto'),
|
||||||
|
@ -14,15 +17,13 @@ let HEROES = [
|
||||||
new Hero(16, 'RubberMan')
|
new Hero(16, 'RubberMan')
|
||||||
];
|
];
|
||||||
|
|
||||||
let heroesPromise = Promise.resolve(HEROES);
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HeroService {
|
export class HeroService {
|
||||||
getHeroes() { return heroesPromise; }
|
getHeroes() { return Observable.of(HEROES); }
|
||||||
|
|
||||||
getHero(id: number | string) {
|
getHero(id: number | string) {
|
||||||
return heroesPromise
|
return this.getHeroes()
|
||||||
// (+) before `id` turns the string into a number
|
// (+) before `id` turns the string into a number
|
||||||
.then(heroes => heroes.find(hero => hero.id === +id));
|
.map(heroes => heroes.find(hero => hero.id === +id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
// #docregion
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { HeroListComponent } from './hero-list.component';
|
||||||
|
import { HeroDetailComponent } from './hero-detail.component';
|
||||||
|
|
||||||
|
const heroesRoutes: Routes = [
|
||||||
|
{ path: 'heroes', component: HeroListComponent },
|
||||||
|
// #docregion hero-detail-route
|
||||||
|
{ path: 'hero/:id', component: HeroDetailComponent }
|
||||||
|
// #enddocregion hero-detail-route
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(heroesRoutes)
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
RouterModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class HeroRoutingModule { }
|
||||||
|
// #enddocregion
|
|
@ -6,10 +6,10 @@ import { HeroListComponent } from './hero-list.component';
|
||||||
import { HeroDetailComponent } from './hero-detail.component';
|
import { HeroDetailComponent } from './hero-detail.component';
|
||||||
|
|
||||||
const heroesRoutes: Routes = [
|
const heroesRoutes: Routes = [
|
||||||
{ path: 'heroes', component: HeroListComponent },
|
{ path: 'heroes', redirectTo: '/superheroes' },
|
||||||
// #docregion hero-detail-route
|
{ path: 'hero/:id', redirectTo: '/superhero/:id' },
|
||||||
{ path: 'hero/:id', component: HeroDetailComponent }
|
{ path: 'superheroes', component: HeroListComponent },
|
||||||
// #enddocregion hero-detail-route
|
{ path: 'superhero/:id', component: HeroDetailComponent }
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -204,6 +204,149 @@ application using the `Router` service and the `routerState` property.
|
||||||
Each `ActivatedRoute` in the `RouterState` provides methods to traverse up and down the route tree
|
Each `ActivatedRoute` in the `RouterState` provides methods to traverse up and down the route tree
|
||||||
to get information from parent, child and sibling routes.
|
to get information from parent, child and sibling routes.
|
||||||
|
|
||||||
|
{@a activated-route}
|
||||||
|
|
||||||
|
|
||||||
|
### Activated route
|
||||||
|
|
||||||
|
The route path and parameters are available through an injected router service called the
|
||||||
|
[ActivatedRoute](api/router/ActivatedRoute).
|
||||||
|
It has a great deal of useful information including:
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
Property
|
||||||
|
</th>
|
||||||
|
|
||||||
|
<th>
|
||||||
|
Description
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>url</code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
An `Observable` of the route path(s), represented as an array of strings for each part of the route path.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>data</code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
An `Observable` that contains the `data` object provided for the route. Also contains any resolved values from the [resolve guard](#resolve-guard).
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>paramMap</code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
An `Observable` that contains a [map](api/router/ParamMap) of the required and [optional parameters](#optional-route-parameters) specific to the route. The map supports retrieving single and multiple values from the same parameter.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>queryParamMap</code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
An `Observable` that contains a [map](api/router/ParamMap) of the [query parameters](#query-parameters) available to all routes.
|
||||||
|
The map supports retrieving single and multiple values from the query parameter.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>fragment</code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
An `Observable` of the URL [fragment](#fragment) available to all routes.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>outlet</code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
The name of the `RouterOutlet` used to render the route. For an unnamed outlet, the outlet name is _primary_.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>routeConfig</code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
The route configuration used for the route that contains the origin path.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>parent</code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
The route's parent `ActivatedRoute` when this route is a [child route](#child-routing-component).
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>firstChild</code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Contains the first `ActivatedRoute` in the list of this route's child routes.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>children</code>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Contains all the [child routes](#child-routing-component) activated under the current route.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="l-sub-section">
|
||||||
|
|
||||||
|
Two older properties are still available. They are less capable than their replacements, discouraged, and may be deprecated in a future Angular version.
|
||||||
|
|
||||||
|
**`params`** — An `Observable` that contains the required and [optional parameters](#optional-route-parameters) specific to the route. Use `paramMap` instead.
|
||||||
|
|
||||||
|
**`queryParams`** — An `Observable` that contains the [query parameters](#query-parameters) available to all routes.
|
||||||
|
Use `queryParamMap` instead.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
### Router events
|
### Router events
|
||||||
|
|
||||||
During each navigation, the `Router` emits navigation events through the `Router.events` property. These events range from when the navigation starts and ends to many points in between. The full list of navigation events is displayed in the table below.
|
During each navigation, the `Router` emits navigation events through the `Router.events` property. These events range from when the navigation starts and ends to many points in between. The full list of navigation events is displayed in the table below.
|
||||||
|
@ -1333,7 +1476,7 @@ Create a new `heroes-routing.module.ts` in the `heroes` folder
|
||||||
using the same techniques you learned while creating the `AppRoutingModule`.
|
using the same techniques you learned while creating the `AppRoutingModule`.
|
||||||
|
|
||||||
|
|
||||||
<code-example path="router/src/app/heroes/heroes-routing.module.ts" title="src/app/heroes/heroes-routing.module.ts">
|
<code-example path="router/src/app/heroes/heroes-routing.module.1.ts" title="src/app/heroes/heroes-routing.module.ts">
|
||||||
|
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
@ -1503,7 +1646,7 @@ Return to the `HeroesRoutingModule` and look at the route definitions again.
|
||||||
The route to `HeroDetailComponent` has a twist.
|
The route to `HeroDetailComponent` has a twist.
|
||||||
|
|
||||||
|
|
||||||
<code-example path="router/src/app/heroes/heroes-routing.module.ts" linenums="false" title="src/app/heroes/heroes-routing.module.ts (excerpt)" region="hero-detail-route">
|
<code-example path="router/src/app/heroes/heroes-routing.module.1.ts" linenums="false" title="src/app/heroes/heroes-routing.module.ts (excerpt)" region="hero-detail-route">
|
||||||
|
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
@ -1547,52 +1690,6 @@ a route for some other hero.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a navigate}
|
|
||||||
|
|
||||||
|
|
||||||
### Navigate to hero detail imperatively
|
|
||||||
|
|
||||||
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 ask the router
|
|
||||||
to navigate to the hero detail view for the selected hero.
|
|
||||||
|
|
||||||
Start in the `HeroListComponent`.
|
|
||||||
Revise its constructor so that it acquires the `Router` and the `HeroService` by dependency injection:
|
|
||||||
|
|
||||||
|
|
||||||
<code-example path="router/src/app/heroes/hero-list.component.1.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (constructor)" region="ctor">
|
|
||||||
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Make the following few changes to the component's template:
|
|
||||||
|
|
||||||
|
|
||||||
<code-example path="router/src/app/heroes/hero-list.component.1.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (template)" region="template">
|
|
||||||
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The template defines an `*ngFor` repeater such as [you've seen before](guide/displaying-data#ngFor).
|
|
||||||
There's a `(click)` [event binding](guide/template-syntax#event-binding) to the component's
|
|
||||||
`onSelect` method which you implement as follows:
|
|
||||||
|
|
||||||
|
|
||||||
<code-example path="router/src/app/heroes/hero-list.component.1.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (select)" region="select">
|
|
||||||
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
{@a route-parameters}
|
{@a route-parameters}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1629,152 +1726,9 @@ the `HeroDetailComponent` via the `ActivatedRoute` service.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a activated-route}
|
{@a activated-route}
|
||||||
|
|
||||||
|
### _Activated Route_ in action
|
||||||
### ActivatedRoute: the one-stop-shop for route information
|
|
||||||
|
|
||||||
The route path and parameters are available through an injected router service called the
|
|
||||||
[ActivatedRoute](api/router/ActivatedRoute).
|
|
||||||
It has a great deal of useful information including:
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
Property
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<th>
|
|
||||||
Description
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<code>url</code>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
An `Observable` of the route path(s), represented as an array of strings for each part of the route path.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<code>data</code>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
An `Observable` that contains the `data` object provided for the route. Also contains any resolved values from the [resolve guard](#resolve-guard).
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<code>paramMap</code>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
An `Observable` that contains a [map](api/router/ParamMap) of the required and [optional parameters](#optional-route-parameters) specific to the route. The map supports retrieving single and multiple values from the same parameter.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<code>queryParamMap</code>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
An `Observable` that contains a [map](api/router/ParamMap) of the [query parameters](#query-parameters) available to all routes.
|
|
||||||
The map supports retrieving single and multiple values from the query parameter.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<code>fragment</code>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
An `Observable` of the URL [fragment](#fragment) available to all routes.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<code>outlet</code>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
The name of the `RouterOutlet` used to render the route. For an unnamed outlet, the outlet name is _primary_.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<code>routeConfig</code>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
The route configuration used for the route that contains the origin path.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<code>parent</code>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
The route's parent `ActivatedRoute` when this route is a [child route](#child-routing-component).
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<code>firstChild</code>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Contains the first `ActivatedRoute` in the list of this route's child routes.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<code>children</code>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Contains all the [child routes](#child-routing-component) activated under the current route.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
Two older properties are still available. They are less capable than their replacements, discouraged, and may be deprecated in a future Angular version.
|
|
||||||
|
|
||||||
**`params`** — An `Observable` that contains the required and [optional parameters](#optional-route-parameters) specific to the route. Use `paramMap` instead.
|
|
||||||
|
|
||||||
**`queryParams`** — An `Observable` that contains the [query parameters](#query-parameters) available to all routes.
|
|
||||||
Use `queryParamMap` instead.
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
#### _Activated Route_ in action
|
|
||||||
|
|
||||||
Import the `Router`, `ActivatedRoute`, and `ParamMap` tokens from the router package.
|
Import the `Router`, `ActivatedRoute`, and `ParamMap` tokens from the router package.
|
||||||
|
|
||||||
|
@ -1820,13 +1774,12 @@ Then you tell the `HeroService` to fetch the hero with that `id` and return the
|
||||||
|
|
||||||
You might think to use the RxJS `map` operator.
|
You might think to use the RxJS `map` operator.
|
||||||
But the `HeroService` returns an `Observable<Hero>`.
|
But the `HeroService` returns an `Observable<Hero>`.
|
||||||
Your subscription wants the `Hero`, not an `Observable<Hero>`.
|
|
||||||
So you flatten the `Observable` with the `switchMap` operator instead.
|
So you flatten the `Observable` with the `switchMap` operator instead.
|
||||||
|
|
||||||
The `switchMap` operator also cancels previous in-flight requests. If the user re-navigates to this route
|
The `switchMap` operator also cancels previous in-flight requests. If the user re-navigates to this route
|
||||||
with a new `id` while the `HeroService` is still retrieving the old `id`, `switchMap` discards that old request and returns the hero for the new `id`.
|
with a new `id` while the `HeroService` is still retrieving the old `id`, `switchMap` discards that old request and returns the hero for the new `id`.
|
||||||
|
|
||||||
Finally, you activate the observable with `subscribe` method and (re)set the component's `hero` property with the retrieved hero.
|
The observable `Subscription` will be handled by the `AsyncPipe` and the component's `hero` property will be (re)set with the retrieved hero.
|
||||||
|
|
||||||
#### _ParamMap_ API
|
#### _ParamMap_ API
|
||||||
|
|
||||||
|
@ -2054,7 +2007,7 @@ The router embedded the `id` value in the navigation URL because you had defined
|
||||||
as a route parameter with an `:id` placeholder token in the route `path`:
|
as a route parameter with an `:id` placeholder token in the route `path`:
|
||||||
|
|
||||||
|
|
||||||
<code-example path="router/src/app/heroes/heroes-routing.module.ts" linenums="false" title="src/app/heroes/heroes-routing.module.ts (hero-detail-route)" region="hero-detail-route">
|
<code-example path="router/src/app/heroes/heroes-routing.module.1.ts" linenums="false" title="src/app/heroes/heroes-routing.module.ts (hero-detail-route)" region="hero-detail-route">
|
||||||
|
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
@ -2190,17 +2143,9 @@ Then you inject the `ActivatedRoute` in the `HeroListComponent` constructor.
|
||||||
The `ActivatedRoute.paramMap` property is an `Observable` map of route parameters. The `paramMap` emits a new map of values that includes `id`
|
The `ActivatedRoute.paramMap` property is an `Observable` map of route parameters. The `paramMap` emits a new map of values that includes `id`
|
||||||
when the user navigates to the component. In `ngOnInit` you subscribe to those values, set the `selectedId`, and get the heroes.
|
when the user navigates to the component. In `ngOnInit` you subscribe to those values, set the `selectedId`, and get the heroes.
|
||||||
|
|
||||||
Add an `isSelected` method that returns `true` when a hero's `id` matches the selected `id`.
|
|
||||||
|
|
||||||
|
Update the template with a [class binding](guide/template-syntax#class-binding).
|
||||||
<code-example path="router/src/app/heroes/hero-list.component.ts" linenums="false" title="src/app/heroes/hero-list.component.ts (isSelected)" region="isSelected">
|
The binding adds the `selected` CSS class when the comparison returns `true` and removes it when `false`.
|
||||||
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Finally, update the template with a [class binding](guide/template-syntax#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:
|
Look for it within the repeated `<li>` tag as shown here:
|
||||||
|
|
||||||
|
|
||||||
|
@ -2439,7 +2384,7 @@ Here are the relevant files for this version of the sample application.
|
||||||
|
|
||||||
</code-pane>
|
</code-pane>
|
||||||
|
|
||||||
<code-pane title="heroes-routing.module.ts" path="router/src/app/heroes/heroes-routing.module.ts">
|
<code-pane title="heroes-routing.module.ts" path="router/src/app/heroes/heroes-routing.module.1.ts">
|
||||||
|
|
||||||
</code-pane>
|
</code-pane>
|
||||||
|
|
||||||
|
@ -2693,40 +2638,15 @@ The router then calculates the target URL based on the active route's location.
|
||||||
{@a nav-to-crisis}
|
{@a nav-to-crisis}
|
||||||
|
|
||||||
|
|
||||||
### Navigate to crisis detail with a relative URL
|
### Navigate to crisis list 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.
|
|
||||||
|
|
||||||
You've already injected the `ActivatedRoute` that you need to compose the relative navigation path.
|
You've already injected the `ActivatedRoute` that you need to compose the relative navigation path.
|
||||||
|
|
||||||
<code-example path="router/src/app/crisis-center/crisis-list.component.ts" linenums="false" title="src/app/crisis-center/crisis-list.component.ts (constructor)" region="ctor">
|
When using a `RouterLink` to navigate instead of the `Router` service, you'd use the _same_
|
||||||
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
When you visit the *Crisis Center*, the ancestor path is `/crisis-center`,
|
|
||||||
so you only need to add the `id` of the *Crisis Center* to the existing path.
|
|
||||||
|
|
||||||
|
|
||||||
<code-example path="router/src/app/crisis-center/crisis-list.component.ts" linenums="false" title="src/app/crisis-center/crisis-list.component.ts (relative navigation)" region="onSelect">
|
|
||||||
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
If you were using a `RouterLink` to navigate instead of the `Router` service, you'd use the _same_
|
|
||||||
link parameters array, but you wouldn't provide the object with the `relativeTo` property.
|
link parameters array, but you wouldn't provide the object with the `relativeTo` property.
|
||||||
The `ActivatedRoute` is implicit in a `RouterLink` directive.
|
The `ActivatedRoute` is implicit in a `RouterLink` directive.
|
||||||
|
|
||||||
|
|
||||||
<code-example path="router/src/app/crisis-center/crisis-list.component.1.ts" linenums="false" title="src/app/crisis-center/crisis-list.component.ts (relative routerLink)" region="relative-navigation-router-link">
|
|
||||||
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Update the `gotoCrises` method of the `CrisisDetailComponent` to navigate back to the *Crisis Center* list using relative path navigation.
|
Update the `gotoCrises` method of the `CrisisDetailComponent` to navigate back to the *Crisis Center* list using relative path navigation.
|
||||||
|
|
||||||
|
|
||||||
|
@ -2735,7 +2655,6 @@ Update the `gotoCrises` method of the `CrisisDetailComponent` to navigate back t
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Notice that the path goes up a level using the `../` syntax.
|
Notice that the path goes up a level using the `../` syntax.
|
||||||
If the current crisis `id` is `3`, the resulting path back to the crisis list is `/crisis-center/;id=3;foo=foo`.
|
If the current crisis `id` is `3`, the resulting path back to the crisis list is `/crisis-center/;id=3;foo=foo`.
|
||||||
|
|
||||||
|
@ -3415,8 +3334,7 @@ is like waiting for the server asynchronously.
|
||||||
|
|
||||||
The `DialogService`, provided in the `AppModule` for app-wide use, does the asking.
|
The `DialogService`, provided in the `AppModule` for app-wide use, does the asking.
|
||||||
|
|
||||||
It returns a [promise](http://exploringjs.com/es6/ch_promises.html) that
|
It returns an `Observable` that *resolves* when the user eventually decides what to do: either
|
||||||
*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`).
|
to discard changes and navigate away (`true`) or to preserve the pending changes and stay in the crisis editor (`false`).
|
||||||
|
|
||||||
|
|
||||||
|
@ -3541,8 +3459,12 @@ Be explicit. Implement the `Resolve` interface with a type of `Crisis`.
|
||||||
Inject the `CrisisService` and `Router` and implement the `resolve()` method.
|
Inject the `CrisisService` and `Router` and implement the `resolve()` method.
|
||||||
That method could return a `Promise`, an `Observable`, or a synchronous return value.
|
That method could return a `Promise`, an `Observable`, or a synchronous return value.
|
||||||
|
|
||||||
The `CrisisService.getCrisis` method returns a promise.
|
The `CrisisService.getCrisis` method returns an Observable.
|
||||||
Return that promise to prevent the route from loading until the data is fetched.
|
Return that observable to prevent the route from loading until the data is fetched.
|
||||||
|
The `Router` guards require an Observable to `complete`, meaning it has emitted all
|
||||||
|
of its values. You use the `take` operator with an argument of `1` to ensure that the
|
||||||
|
Observable completes after retrieving the first value from the Observable returned by the
|
||||||
|
`getCrisis` method.
|
||||||
If it doesn't return a valid `Crisis`, navigate the user back to the `CrisisListComponent`,
|
If it doesn't return a valid `Crisis`, navigate the user back to the `CrisisListComponent`,
|
||||||
canceling the previous in-flight navigation to the `CrisisDetailComponent`.
|
canceling the previous in-flight navigation to the `CrisisDetailComponent`.
|
||||||
|
|
||||||
|
@ -3580,12 +3502,15 @@ The router looks for that method and calls it if found.
|
||||||
Don't worry about all the ways that the user could navigate away.
|
Don't worry about all the ways that the user could navigate away.
|
||||||
That's the router's job. Write this class and let the router take it from there.
|
That's the router's job. Write this class and let the router take it from there.
|
||||||
|
|
||||||
|
1. The Observable provided to the Router _must_ complete.
|
||||||
|
If the Observable does not complete, the navigation will not continue.
|
||||||
|
|
||||||
The relevant *Crisis Center* code for this milestone follows.
|
The relevant *Crisis Center* code for this milestone follows.
|
||||||
|
|
||||||
|
|
||||||
<code-tabs>
|
<code-tabs>
|
||||||
|
|
||||||
<code-pane title="app.component.ts" path="router/src/app/app.component.ts">
|
<code-pane title="app.component.ts" path="router/src/app/app.component.6.ts">
|
||||||
|
|
||||||
</code-pane>
|
</code-pane>
|
||||||
|
|
||||||
|
@ -4041,6 +3966,52 @@ Verify this by logging in to the `Admin` feature area and noting that the `crisi
|
||||||
It's also logged to the browser's console.
|
It's also logged to the browser's console.
|
||||||
|
|
||||||
|
|
||||||
|
{@a redirect-advanced}
|
||||||
|
|
||||||
|
## Migrating URLs with Redirects
|
||||||
|
|
||||||
|
You've setup the routes for navigating around your application. You've used navigation imperatively and declaratively to many different routes. But like any application, requirements change over time. You've setup links and navigation to `/heroes` and `/hero/:id` from the `HeroListComponent` and `HeroDetailComponent` components. If there was a requirement that links to `heroes` become `superheroes`, you still want the previous URLs to navigate correctly. You also don't want to go and update every link in your application, so redirects makes refactoring routes trivial.
|
||||||
|
|
||||||
|
|
||||||
|
{@a url-refactor}
|
||||||
|
|
||||||
|
### Changing /heroes to /superheroes
|
||||||
|
|
||||||
|
Let's take the `Hero` routes and migrate them to new URLs. The `Router` checks for redirects in your configuration before navigating, so each redirect is triggered when needed. To support this change, you'll add redirects from the old routes to the new routes in the `heroes-routing.module`.
|
||||||
|
|
||||||
|
<code-example path="router/src/app/heroes/heroes-routing.module.ts" linenums="false" title="src/app/heroes/heroes-routing.module.ts (heroes redirects)">
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
You'll notice two different types of redirects. The first change is from `/heroes` to `/superheroes` without any parameters. This is a straightforward redirect, unlike the change from `/hero/:id` to `/superhero/:id`, which includes the `:id` route parameter. Router redirects also use powerful pattern matching, so the `Router` inspects the URL and replaces route parameters in the `path` with their appropriate destination. Previously, you navigated to a URL such as `/hero/15` with a route parameter `id` of `15`.
|
||||||
|
|
||||||
|
<div class="l-sub-section">
|
||||||
|
|
||||||
|
The `Router` also supports [query parameters](#query-parameters) and the [fragment](#fragment) when using redirects.
|
||||||
|
|
||||||
|
* When using absolute redirects, the `Router` will use the query parameters and the fragment from the redirectTo in the route config.
|
||||||
|
* When using relative redirects, the `Router` use the query params and the fragment from the source URL.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Before updating the `app-routing.module.ts`, you'll need to consider an important rule. Currently, our empty path route redirects to `/heroes`, which redirects to `/superheroes`. This _won't_ work and is by design as the `Router` handles redirects once at each level of routing configuration. This prevents chaining of redirects, which can lead to endless redirect loops.
|
||||||
|
|
||||||
|
So instead, you'll update the empty path route in `app-routing.module.ts` to redirect to `/superheroes`.
|
||||||
|
|
||||||
|
<code-example path="router/src/app/app-routing.module.ts" linenums="false" title="src/app/app-routing.module.ts (superheroes redirect)">
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
Since `RouterLink`s aren't tied to route configuration, you'll need to update the associated router links so they remain active when the new route is active. You'll update the `app.component.ts` template for the `/heroes` routerLink.
|
||||||
|
|
||||||
|
<code-example path="router/src/app/app.component.ts" linenums="false" title="src/app/app.component.ts (superheroes active routerLink)">
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
With the redirects setup, all previous routes now point to their new destinations and both URLs still function as intended.
|
||||||
|
|
||||||
|
|
||||||
{@a inspect-config}
|
{@a inspect-config}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue