docs(router): Added section for named outlets and secondary routes (#2842)
This commit is contained in:
parent
b47757a0fb
commit
397c337378
|
@ -28,21 +28,25 @@ describe('Router', function () {
|
||||||
|
|
||||||
adminHref: hrefEles.get(2),
|
adminHref: hrefEles.get(2),
|
||||||
adminPreloadList: element.all(by.css('my-app > ng-component > ng-component > ul > li')),
|
adminPreloadList: element.all(by.css('my-app > ng-component > ng-component > ul > li')),
|
||||||
|
|
||||||
loginHref: hrefEles.get(3),
|
loginHref: hrefEles.get(3),
|
||||||
loginButton: element.all(by.css('my-app > ng-component > p > button')),
|
loginButton: element.all(by.css('my-app > ng-component > p > button')),
|
||||||
|
|
||||||
sidekicksButton: element.all(by.css('my-app > ng-component > button')),
|
contactHref: hrefEles.get(4),
|
||||||
|
contactCancelButton: element.all(by.buttonText('Cancel')),
|
||||||
|
|
||||||
|
outletComponents: element.all(by.css('my-app > ng-component'))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should be able to see the start screen', function () {
|
it('should be able to see the start screen', function () {
|
||||||
let page = getPageStruct();
|
let page = getPageStruct();
|
||||||
expect(page.hrefs.count()).toEqual(4, 'should be 4 dashboard choices');
|
expect(page.hrefs.count()).toEqual(5, 'should be 5 dashboard choices');
|
||||||
expect(page.crisisHref.getText()).toEqual('Crisis Center');
|
expect(page.crisisHref.getText()).toEqual('Crisis Center');
|
||||||
expect(page.heroesHref.getText()).toEqual('Heroes');
|
expect(page.heroesHref.getText()).toEqual('Heroes');
|
||||||
expect(page.adminHref.getText()).toEqual('Admin');
|
expect(page.adminHref.getText()).toEqual('Admin');
|
||||||
expect(page.loginHref.getText()).toEqual('Login');
|
expect(page.loginHref.getText()).toEqual('Login');
|
||||||
|
expect(page.contactHref.getText()).toEqual('Contact');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to see crises center items', function () {
|
it('should be able to see crises center items', function () {
|
||||||
|
@ -120,12 +124,12 @@ describe('Router', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to handle 404 pages', function () {
|
it('should be able to see the secondary route', function () {
|
||||||
let page = getPageStruct();
|
let page = getPageStruct();
|
||||||
page.heroesHref.click().then(function() {
|
page.heroesHref.click().then(function() {
|
||||||
return page.sidekicksButton.click();
|
return page.contactHref.click();
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
expect(page.routerTitle.getText()).toContain('Page Not Found');
|
expect(page.outletComponents.count()).toBe(2, 'should be 2 displayed routes');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
// #docplaster
|
// #docplaster
|
||||||
// #docregion
|
// #docregion , v3
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { PageNotFoundComponent }from './not-found.component';
|
import { ComposeMessageComponent } from './compose-message.component';
|
||||||
|
|
||||||
const appRoutes: Routes = [
|
const appRoutes: Routes = [
|
||||||
{ path: '**', component: PageNotFoundComponent }
|
// #enddocregion v3
|
||||||
|
{
|
||||||
|
path: 'compose',
|
||||||
|
component: ComposeMessageComponent,
|
||||||
|
outlet: 'modal'
|
||||||
|
}
|
||||||
|
// #docregion v3
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
// #docplaster
|
|
||||||
// #docregion
|
// #docregion
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { PageNotFoundComponent }from './not-found.component';
|
import { PageNotFoundComponent }from './not-found.component';
|
||||||
|
|
||||||
|
import { ComposeMessageComponent } from './compose-message.component';
|
||||||
import { CanDeactivateGuard } from './can-deactivate-guard.service';
|
import { CanDeactivateGuard } from './can-deactivate-guard.service';
|
||||||
|
|
||||||
const appRoutes: Routes = [
|
const appRoutes: Routes = [
|
||||||
{ path: '**', component: PageNotFoundComponent }
|
{
|
||||||
|
path: 'compose',
|
||||||
|
component: ComposeMessageComponent,
|
||||||
|
outlet: 'modal'
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -4,7 +4,8 @@ import { NgModule } from '@angular/core';
|
||||||
// #docregion import-router
|
// #docregion import-router
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
// #enddocregion import-router
|
// #enddocregion import-router
|
||||||
import { PageNotFoundComponent } from './not-found.component';
|
|
||||||
|
import { ComposeMessageComponent } from './compose-message.component';
|
||||||
import { CanDeactivateGuard } from './can-deactivate-guard.service';
|
import { CanDeactivateGuard } from './can-deactivate-guard.service';
|
||||||
// #docregion can-load-guard
|
// #docregion can-load-guard
|
||||||
import { AuthGuard } from './auth-guard.service';
|
import { AuthGuard } from './auth-guard.service';
|
||||||
|
@ -13,6 +14,11 @@ import { AuthGuard } from './auth-guard.service';
|
||||||
// #docregion lazy-load-admin, can-load-guard
|
// #docregion lazy-load-admin, can-load-guard
|
||||||
|
|
||||||
const appRoutes: Routes = [
|
const appRoutes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'compose',
|
||||||
|
component: ComposeMessageComponent,
|
||||||
|
outlet: 'modal'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'admin',
|
path: 'admin',
|
||||||
loadChildren: 'app/admin/admin.module#AdminModule',
|
loadChildren: 'app/admin/admin.module#AdminModule',
|
||||||
|
|
|
@ -8,11 +8,16 @@ import {
|
||||||
// #docregion preload-v1
|
// #docregion preload-v1
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
import { PageNotFoundComponent } from './not-found.component';
|
import { ComposeMessageComponent } from './compose-message.component';
|
||||||
import { CanDeactivateGuard } from './can-deactivate-guard.service';
|
import { CanDeactivateGuard } from './can-deactivate-guard.service';
|
||||||
import { AuthGuard } from './auth-guard.service';
|
import { AuthGuard } from './auth-guard.service';
|
||||||
|
|
||||||
const appRoutes: Routes = [
|
const appRoutes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'compose',
|
||||||
|
component: ComposeMessageComponent,
|
||||||
|
outlet: 'modal'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'admin',
|
path: 'admin',
|
||||||
loadChildren: 'app/admin/admin.module#AdminModule',
|
loadChildren: 'app/admin/admin.module#AdminModule',
|
||||||
|
|
|
@ -3,12 +3,17 @@
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
import { PageNotFoundComponent } from './not-found.component';
|
import { ComposeMessageComponent } from './compose-message.component';
|
||||||
import { CanDeactivateGuard } from './can-deactivate-guard.service';
|
import { CanDeactivateGuard } from './can-deactivate-guard.service';
|
||||||
import { AuthGuard } from './auth-guard.service';
|
import { AuthGuard } from './auth-guard.service';
|
||||||
import { PreloadSelectedModules } from './selective-preload-strategy';
|
import { PreloadSelectedModules } from './selective-preload-strategy';
|
||||||
|
|
||||||
const appRoutes: Routes = [
|
const appRoutes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'compose',
|
||||||
|
component: ComposeMessageComponent,
|
||||||
|
outlet: 'modal'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'admin',
|
path: 'admin',
|
||||||
loadChildren: 'app/admin/admin.module#AdminModule',
|
loadChildren: 'app/admin/admin.module#AdminModule',
|
||||||
|
@ -26,10 +31,6 @@ const appRoutes: Routes = [
|
||||||
data: {
|
data: {
|
||||||
preload: true
|
preload: true
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '**',
|
|
||||||
component: PageNotFoundComponent
|
|
||||||
}
|
}
|
||||||
// #enddocregion preload-v2
|
// #enddocregion preload-v2
|
||||||
];
|
];
|
||||||
|
|
|
@ -12,6 +12,9 @@ import { Component } from '@angular/core';
|
||||||
<a routerLink="/admin" routerLinkActive="active">Admin</a>
|
<a routerLink="/admin" routerLinkActive="active">Admin</a>
|
||||||
</nav>
|
</nav>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
// #enddocregion template
|
||||||
|
<router-outlet name="modal"></router-outlet>
|
||||||
|
// #enddocregion template
|
||||||
`
|
`
|
||||||
// #enddocregion template
|
// #enddocregion template
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,8 +12,10 @@ import { Component } from '@angular/core';
|
||||||
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
|
<a routerLink="/heroes" 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: { modal: ['compose'] } }]">Contact</a>
|
||||||
</nav>
|
</nav>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
<router-outlet name="modal"></router-outlet>
|
||||||
`
|
`
|
||||||
// #enddocregion template
|
// #enddocregion template
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,11 +8,11 @@ import { FormsModule } from '@angular/forms';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { PageNotFoundComponent } from './not-found.component';
|
import { PageNotFoundComponent } from './not-found.component';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
|
||||||
import { HeroesModule } from './heroes/heroes.module';
|
import { HeroesModule } from './heroes/heroes.module';
|
||||||
// #docregion crisis-center-module
|
// #docregion crisis-center-module
|
||||||
import { CrisisCenterModule } from './crisis-center/crisis-center.module';
|
import { CrisisCenterModule } from './crisis-center/crisis-center.module';
|
||||||
// #enddocregion crisis-center-module
|
// #enddocregion crisis-center-module
|
||||||
|
import { ComposeMessageComponent } from './compose-message.component';
|
||||||
// #docregion admin-module
|
// #docregion admin-module
|
||||||
import { AdminModule } from './admin/admin.module';
|
import { AdminModule } from './admin/admin.module';
|
||||||
// #docregion crisis-center-module
|
// #docregion crisis-center-module
|
||||||
|
@ -26,13 +26,16 @@ import { DialogService } from './dialog.service';
|
||||||
HeroesModule,
|
HeroesModule,
|
||||||
CrisisCenterModule,
|
CrisisCenterModule,
|
||||||
// #enddocregion crisis-center-module
|
// #enddocregion crisis-center-module
|
||||||
|
// #enddocregion admin-module
|
||||||
AdminModule,
|
AdminModule,
|
||||||
// #docregion crisis-center-module
|
// #docregion crisis-center-module
|
||||||
AppRoutingModule
|
AppRoutingModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
PageNotFoundComponent
|
// #enddocregion admin-module, crisis-center-module
|
||||||
|
ComposeMessageComponent
|
||||||
|
// #docregion admin-module, crisis-center-module
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DialogService
|
DialogService
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
|
// #docplaster
|
||||||
// #docregion
|
// #docregion
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { PageNotFoundComponent }from './not-found.component';
|
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
|
||||||
import { HeroesModule } from './heroes/heroes.module';
|
import { HeroesModule } from './heroes/heroes.module';
|
||||||
import { CrisisCenterModule } from './crisis-center/crisis-center.module';
|
import { CrisisCenterModule } from './crisis-center/crisis-center.module';
|
||||||
import { AdminModule } from './admin/admin.module';
|
import { ComposeMessageComponent } from './compose-message.component';
|
||||||
|
|
||||||
|
import { AdminModule } from './admin/admin.module';
|
||||||
import { DialogService } from './dialog.service';
|
import { DialogService } from './dialog.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
HeroesModule,
|
HeroesModule,
|
||||||
CrisisCenterModule,
|
CrisisCenterModule,
|
||||||
|
@ -24,7 +25,7 @@ import { DialogService } from './dialog.service';
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
PageNotFoundComponent
|
ComposeMessageComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DialogService
|
DialogService
|
||||||
|
@ -33,3 +34,4 @@ import { DialogService } from './dialog.service';
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
}
|
}
|
||||||
|
// #enddocregion
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { AppRoutingModule } from './app-routing.module';
|
||||||
|
|
||||||
import { HeroesModule } from './heroes/heroes.module';
|
import { HeroesModule } from './heroes/heroes.module';
|
||||||
import { CrisisCenterModule } from './crisis-center/crisis-center.module';
|
import { CrisisCenterModule } from './crisis-center/crisis-center.module';
|
||||||
|
import { ComposeMessageComponent } from './compose-message.component';
|
||||||
|
|
||||||
import { LoginRoutingModule } from './login-routing.module';
|
import { LoginRoutingModule } from './login-routing.module';
|
||||||
import { LoginComponent } from './login.component';
|
import { LoginComponent } from './login.component';
|
||||||
|
|
||||||
|
@ -25,7 +27,7 @@ import { DialogService } from './dialog.service';
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
PageNotFoundComponent,
|
ComposeMessageComponent,
|
||||||
LoginComponent
|
LoginComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { PageNotFoundComponent } from './not-found.component';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
|
||||||
import { HeroesModule } from './heroes/heroes.module';
|
import { HeroesModule } from './heroes/heroes.module';
|
||||||
|
import { ComposeMessageComponent } from './compose-message.component';
|
||||||
import { LoginRoutingModule } from './login-routing.module';
|
import { LoginRoutingModule } from './login-routing.module';
|
||||||
import { LoginComponent } from './login.component';
|
import { LoginComponent } from './login.component';
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ import { DialogService } from './dialog.service';
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
PageNotFoundComponent,
|
ComposeMessageComponent,
|
||||||
LoginComponent
|
LoginComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
// #docplaster
|
||||||
|
// #docregion
|
||||||
|
// #docregion v1
|
||||||
|
import 'rxjs/add/observable/of';
|
||||||
|
import 'rxjs/add/operator/delay';
|
||||||
|
import 'rxjs/add/operator/do';
|
||||||
|
import { Component, HostBinding,
|
||||||
|
trigger, transition,
|
||||||
|
animate, style, state } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<h3>Contact Crisis Center</h3>
|
||||||
|
<div *ngIf="details">
|
||||||
|
{{ details }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<label>Message: </label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea [(ngModel)]="message" rows="10" cols="35" [disabled]="sending"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p *ngIf="!sending">
|
||||||
|
<button (click)="send()">Send</button>
|
||||||
|
// #enddocregion v1
|
||||||
|
<button (click)="cancel()">Cancel</button>
|
||||||
|
// #docregion v1
|
||||||
|
</p>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
:host {
|
||||||
|
position: relative;
|
||||||
|
bottom: 10%;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
],
|
||||||
|
animations: [
|
||||||
|
trigger('routeAnimation', [
|
||||||
|
state('*',
|
||||||
|
style({
|
||||||
|
opacity: 1,
|
||||||
|
transform: 'translateX(0)'
|
||||||
|
})
|
||||||
|
),
|
||||||
|
transition(':enter', [
|
||||||
|
style({
|
||||||
|
opacity: 0,
|
||||||
|
transform: 'translateY(100%)'
|
||||||
|
}),
|
||||||
|
animate('0.2s ease-in')
|
||||||
|
]),
|
||||||
|
transition(':leave', [
|
||||||
|
animate('0.5s ease-out', style({
|
||||||
|
opacity: 0,
|
||||||
|
transform: 'translateY(100%)'
|
||||||
|
}))
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ComposeMessageComponent {
|
||||||
|
@HostBinding('@routeAnimation') get routeAnimation() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostBinding('style.display') get display() {
|
||||||
|
return 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostBinding('style.position') get position() {
|
||||||
|
return 'absolute';
|
||||||
|
}
|
||||||
|
|
||||||
|
details: string;
|
||||||
|
sending: boolean = false;
|
||||||
|
|
||||||
|
constructor(private router: Router) {}
|
||||||
|
|
||||||
|
send() {
|
||||||
|
this.sending = true;
|
||||||
|
this.details = 'Sending Message...';
|
||||||
|
|
||||||
|
Observable.of(true)
|
||||||
|
.delay(1000)
|
||||||
|
.do(() => {
|
||||||
|
this.sending = false;
|
||||||
|
// #enddocregion v1
|
||||||
|
this.closeModal();
|
||||||
|
// #docregion v1
|
||||||
|
}).subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
// #enddocregion v1
|
||||||
|
closeModal() {
|
||||||
|
this.router.navigate(['/', { outlets: { modal: null }}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.closeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #enddocregion
|
|
@ -0,0 +1,105 @@
|
||||||
|
// #docregion
|
||||||
|
import 'rxjs/add/observable/of';
|
||||||
|
import 'rxjs/add/operator/delay';
|
||||||
|
import 'rxjs/add/operator/do';
|
||||||
|
import { Component, HostBinding,
|
||||||
|
trigger, transition,
|
||||||
|
animate, style, state } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<h3>Contact Crisis Center</h3>
|
||||||
|
<div *ngIf="details">
|
||||||
|
{{ details }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<label>Message: </label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<textarea [(ngModel)]="message" rows="10" cols="35" [disabled]="sending"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p *ngIf="!sending">
|
||||||
|
<button (click)="send()">Send</button>
|
||||||
|
<button (click)="cancel()">Cancel</button>
|
||||||
|
</p>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
:host {
|
||||||
|
position: relative;
|
||||||
|
bottom: 10%;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
],
|
||||||
|
animations: [
|
||||||
|
trigger('routeAnimation', [
|
||||||
|
state('*',
|
||||||
|
style({
|
||||||
|
opacity: 1,
|
||||||
|
transform: 'translateX(0)'
|
||||||
|
})
|
||||||
|
),
|
||||||
|
transition(':enter', [
|
||||||
|
style({
|
||||||
|
opacity: 0,
|
||||||
|
transform: 'translateY(100%)'
|
||||||
|
}),
|
||||||
|
animate('0.2s ease-in')
|
||||||
|
]),
|
||||||
|
transition(':leave', [
|
||||||
|
animate('0.5s ease-out', style({
|
||||||
|
opacity: 0,
|
||||||
|
transform: 'translateY(100%)'
|
||||||
|
}))
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ComposeMessageComponent {
|
||||||
|
@HostBinding('@routeAnimation') get routeAnimation() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostBinding('style.display') get display() {
|
||||||
|
return 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostBinding('style.position') get position() {
|
||||||
|
return 'absolute';
|
||||||
|
}
|
||||||
|
|
||||||
|
details: string;
|
||||||
|
sending: boolean = false;
|
||||||
|
|
||||||
|
constructor(private router: Router) {}
|
||||||
|
|
||||||
|
send() {
|
||||||
|
this.sending = true;
|
||||||
|
this.details = 'Sending Message...';
|
||||||
|
|
||||||
|
Observable.of(true)
|
||||||
|
.delay(1000)
|
||||||
|
.do(() => {
|
||||||
|
this.sending = false;
|
||||||
|
|
||||||
|
// Close the modal
|
||||||
|
this.closeModal();
|
||||||
|
}).subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
closeModal() {
|
||||||
|
// Providing a `null` value to the named outlet
|
||||||
|
// clears the contents of the named outlet
|
||||||
|
this.router.navigate([{ outlets: { modal: null }}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
// Close the modal
|
||||||
|
this.closeModal();
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,7 @@ include ../../../_includes/_see-addr-bar
|
||||||
* refactoring routing into a [routing module](#routing-module)
|
* refactoring routing into a [routing module](#routing-module)
|
||||||
* add [child routes](#child-routing-component) under a feature section
|
* add [child routes](#child-routing-component) under a feature section
|
||||||
* [grouping child routes](#component-less-route) without a component
|
* [grouping child routes](#component-less-route) without a component
|
||||||
|
* displaying [multiple routes](#named-outlets) in separate outlets
|
||||||
* [redirecting](#redirect) from one route to another
|
* [redirecting](#redirect) from one route to another
|
||||||
* confirming or canceling navigation with [guards](#guards)
|
* confirming or canceling navigation with [guards](#guards)
|
||||||
* [CanActivate](#can-activate-guard) to prevent navigation to a route
|
* [CanActivate](#can-activate-guard) to prevent navigation to a route
|
||||||
|
@ -473,7 +474,7 @@ a#router-outlet
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
A template may hold exactly one ***unnamed*** `<router-outlet>`.
|
A template may hold exactly one ***unnamed*** `<router-outlet>`.
|
||||||
The router supports multiple *named* outlets, a feature we'll cover in future.
|
The router supports multiple [named outlets](#named-outlets), covered later in the chapter.
|
||||||
|
|
||||||
a#router-link
|
a#router-link
|
||||||
:marked
|
:marked
|
||||||
|
@ -1313,7 +1314,7 @@ h3#merge-hero-routes Import hero module into AppModule
|
||||||
router/ts/app/heroes/hero.service.ts,
|
router/ts/app/heroes/hero.service.ts,
|
||||||
router/ts/app/heroes/heroes.module.ts,
|
router/ts/app/heroes/heroes.module.ts,
|
||||||
router/ts/app/heroes/heroes-routing.module.ts`,
|
router/ts/app/heroes/heroes-routing.module.ts`,
|
||||||
null,
|
'null,null,v3,null,null,null,null,null',
|
||||||
`app.component.ts,
|
`app.component.ts,
|
||||||
app.module.ts,
|
app.module.ts,
|
||||||
app-routing.module.ts,
|
app-routing.module.ts,
|
||||||
|
@ -1353,6 +1354,8 @@ h3#merge-hero-routes Import hero module into AppModule
|
||||||
|
|
||||||
* The router should block access to certain features until the user logs-in.
|
* The router should block access to certain features until the user logs-in.
|
||||||
|
|
||||||
|
* The application should display multiple routes indepdently of each other.
|
||||||
|
|
||||||
* Changes to a feature module such as *Crisis Center* shouldn't provoke changes to the `AppModule` or
|
* Changes to a feature module such as *Crisis Center* shouldn't provoke changes to the `AppModule` or
|
||||||
any other feature's component.
|
any other feature's component.
|
||||||
We need to [*separate our concerns*](https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html).
|
We need to [*separate our concerns*](https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html).
|
||||||
|
@ -1478,7 +1481,7 @@ h3#import-crisis-module Import crisis center module into the AppModule routes
|
||||||
are now being provided by our `HeroesModule` and our `CrisisCenter` feature modules. We'll keep our `app-routing.module.ts` file
|
are now being provided by our `HeroesModule` and our `CrisisCenter` feature modules. We'll keep our `app-routing.module.ts` file
|
||||||
for general routes which we'll cover later in the chapter.
|
for general routes which we'll cover later in the chapter.
|
||||||
|
|
||||||
+makeExcerpt('app/app-routing.module.3.ts (v3)', '')
|
+makeExcerpt('app/app-routing.module.3.ts (v3)', 'v3')
|
||||||
|
|
||||||
a#redirect
|
a#redirect
|
||||||
:marked
|
:marked
|
||||||
|
@ -1603,6 +1606,103 @@ h2#relative-navigation Relative Navigation
|
||||||
|
|
||||||
+makeExcerpt('app/crisis-center/crisis-list.component.1.ts (relative routerLink)', 'relative-navigation-router-link')
|
+makeExcerpt('app/crisis-center/crisis-list.component.1.ts (relative routerLink)', 'relative-navigation-router-link')
|
||||||
|
|
||||||
|
<a id="secondary-routes"></a>
|
||||||
|
.l-main-section
|
||||||
|
h3#named-outlets Displaying Multiple Routes: Named Outlets and Secondary Routes
|
||||||
|
:marked
|
||||||
|
Up until now, we've used a single outlet and we've nested child routes under that outlet to group routes together.
|
||||||
|
The `Router` supports one primary `unnamed` outlet, but we can display multiple routes together, each displaying their own component
|
||||||
|
using one or more **named outlets**.
|
||||||
|
|
||||||
|
These named outlets are used to display **secondary routes**, which are configured the same way as a primary route, but
|
||||||
|
are independent of each other and can work in combination with each other routes. By using named outlets with secondary routes,
|
||||||
|
we can display multiple route components simultaneously. Secondary routes can also have their own child routes. This allows us to reflect the state of multiple
|
||||||
|
views in our application using the browser URL.
|
||||||
|
|
||||||
|
In our application, we want to give our users a way to contact the `Crisis Center` displayed through a modal. We also want this modal
|
||||||
|
to be displayed even when switching between pages in our application. We'll use a named outlet to display the modal and control its lifecycle
|
||||||
|
alongside our currently displayed route.
|
||||||
|
|
||||||
|
We'll create a new route component named `ComposeMessageComponent` in `app/compose-message.component.ts` to display our modal view the users will
|
||||||
|
use to contact the `Crisis Center`. We'll display a simple form to enter a message and a use an `Observable` to simulate a delay to handle the modal after
|
||||||
|
sending the message.
|
||||||
|
|
||||||
|
+makeExcerpt('app/compose-message.component.1.ts (compose message component)', 'v1')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
We'll add a `compose` route to our `AppRoutingModule`, using a route `path` and `component`. In order to use a named outlet, we add an additional property
|
||||||
|
to our route configuration called **outlet**. This outlet matches the name given to our `RouterOutlet` where we are going to to display the component. The `Router`
|
||||||
|
will place each route next to its intended `RouterOutlet` during the navigation process.
|
||||||
|
|
||||||
|
+makeExcerpt('app/app-routing.module.3.ts (compose route)', '')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
As with our other components, the `ComposeMessageComponent` is imported and added to our `AppModule`'s declarations.
|
||||||
|
|
||||||
|
+makeExcerpt('app/app.module.5.ts (compose component)', '')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
As we did in our initial template, we'll add a `RouterOutlet` with an additional **name** attribute and provide our named **modal** outlet. This matches the
|
||||||
|
name of the `outlet` we used in our `AppRoutingModule`.
|
||||||
|
|
||||||
|
+makeExcerpt('app/app.component.4.ts (named outlet)', '')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
Now we have two independent `RouterOutlet`s to display our routes. Routes without a specified `outlet` will use our primary `unnamed` outlet. Our `compose` outlet
|
||||||
|
will display our `ComposeMessageComponent`. Secondary routes are also reflected in our URL surrounded by parenthesis but are are seamlessly
|
||||||
|
handled by the `Router` across browsers the same way it handles [route parameters with matrix notation](#optional-route-parameters). We can visit any primary route in our application
|
||||||
|
and have our `compose` route displayed in combination with the primary route. An example URL displaying the `Crisis Center` list and `Compose` modal is displayed below.
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
http://localhost:3000/crisis-center(modal:compose)
|
||||||
|
|
||||||
|
:marked
|
||||||
|
If we break down the URL, we see the following parts.
|
||||||
|
* The primary route with the `crisis-center` path
|
||||||
|
* The parenthesis where our secondary route grouping starts and ends
|
||||||
|
* The name of the outlet (`modal`) split by a `colon` and followed with the secondary route path `compose`
|
||||||
|
|
||||||
|
Secondary routes are not limited to top level routes. Each level in our route configuration can contain a primary route in an unnamed outlet,
|
||||||
|
followed by any number of secondary routes displayed in named outlets. Secondary routes have all the same features of a primary route, including
|
||||||
|
access to their own [activated route](#activated-route).
|
||||||
|
|
||||||
|
h3#secondary-route-navigation <i>Secondary Route Navigation</i>: merging routes during navigation
|
||||||
|
:marked
|
||||||
|
We learned how the `Router` handles incoming URLs for secondary routes, but we can also navigate declaratively and imperatively using secondary routes
|
||||||
|
in the [link parameters array](#link-parameters-array). Let's update our application to display our `compose` route using `RouterLink` and navigating
|
||||||
|
away from a secondary route using the `Router` service.
|
||||||
|
|
||||||
|
The `Link Parameters Array` supports secondary routes with an object as the last index in our array. This object uses an **outlets** that lets us build
|
||||||
|
our links including secondary outlets in the same way we build more dynamic router links. We'll add a `Contact` link to our `Crisis Center` menu to display our
|
||||||
|
secondary route.
|
||||||
|
|
||||||
|
+makeExcerpt('app/app.component.ts (contact)', '')
|
||||||
|
|
||||||
|
:marked
|
||||||
|
The `outlets` property within our object contains the named outlets that will be updated during the navigation process. We'll add a key for the `modal` outlet
|
||||||
|
and the value for the outlet is the same link parameters array we use with primary routes. When using a `RouterLink` or navigating imperatively, the `Router`
|
||||||
|
will **merge** the current URL with our provided secondary route. As we navigate around our application, the `Contact` link will be updated with the current URL
|
||||||
|
and the secondary `(modal:compose)` route, so that we can open our `compose` route independently of our currently displayed route. Once we visit the secondary route,
|
||||||
|
it will be merged with the current URL across each navigation as long as they're under the same parent path.
|
||||||
|
|
||||||
|
.l-sub-section
|
||||||
|
:marked
|
||||||
|
When using router's `navigateByUrl` method, secondary routes will **not** be merged into the current URL and will need to be explicitly
|
||||||
|
provided.
|
||||||
|
|
||||||
|
:marked
|
||||||
|
#### Clearing Secondary Routes
|
||||||
|
Secondary outlets persist across navigation under the same parent route. We can also remove secondary outlets using a `RouterLink` or imperatively
|
||||||
|
with the `Router` service using the [link parameters array](#link-parameters-array). We'll update our `ComposeMessageComponent` to remove the
|
||||||
|
secondary `compose` route when our user cancels the message or its sent successfully.
|
||||||
|
|
||||||
|
We'll add a `closeModal` method to our `ComposeMessageComponent` that navigates imperatively using the `router.navigate` method. Since we only want to modify the
|
||||||
|
secondary route, we only need to provide the secondary route object and `null` out the `modal` outlet. This will remove the secondary route from named `modal` `RouterOutlet`
|
||||||
|
and update the current URL.
|
||||||
|
|
||||||
|
+makeExcerpt('app/compose-message.component.ts (remove secondary route)', '')
|
||||||
|
|
||||||
<a id="guards"></a>
|
<a id="guards"></a>
|
||||||
.l-main-section
|
.l-main-section
|
||||||
h2#guards Route Guards
|
h2#guards Route Guards
|
||||||
|
|
Loading…
Reference in New Issue