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),
|
||||
adminPreloadList: element.all(by.css('my-app > ng-component > ng-component > ul > li')),
|
||||
|
||||
loginHref: hrefEles.get(3),
|
||||
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 () {
|
||||
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.heroesHref.getText()).toEqual('Heroes');
|
||||
expect(page.adminHref.getText()).toEqual('Admin');
|
||||
expect(page.loginHref.getText()).toEqual('Login');
|
||||
expect(page.contactHref.getText()).toEqual('Contact');
|
||||
});
|
||||
|
||||
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();
|
||||
page.heroesHref.click().then(function() {
|
||||
return page.sidekicksButton.click();
|
||||
return page.contactHref.click();
|
||||
}).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
|
||||
// #docregion
|
||||
// #docregion , v3
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { PageNotFoundComponent }from './not-found.component';
|
||||
import { ComposeMessageComponent } from './compose-message.component';
|
||||
|
||||
const appRoutes: Routes = [
|
||||
{ path: '**', component: PageNotFoundComponent }
|
||||
// #enddocregion v3
|
||||
{
|
||||
path: 'compose',
|
||||
component: ComposeMessageComponent,
|
||||
outlet: 'modal'
|
||||
}
|
||||
// #docregion v3
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
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';
|
||||
|
||||
const appRoutes: Routes = [
|
||||
{ path: '**', component: PageNotFoundComponent }
|
||||
{
|
||||
path: 'compose',
|
||||
component: ComposeMessageComponent,
|
||||
outlet: 'modal'
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -4,7 +4,8 @@ import { NgModule } from '@angular/core';
|
|||
// #docregion import-router
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
// #enddocregion import-router
|
||||
import { PageNotFoundComponent } from './not-found.component';
|
||||
|
||||
import { ComposeMessageComponent } from './compose-message.component';
|
||||
import { CanDeactivateGuard } from './can-deactivate-guard.service';
|
||||
// #docregion can-load-guard
|
||||
import { AuthGuard } from './auth-guard.service';
|
||||
|
@ -13,6 +14,11 @@ import { AuthGuard } from './auth-guard.service';
|
|||
// #docregion lazy-load-admin, can-load-guard
|
||||
|
||||
const appRoutes: Routes = [
|
||||
{
|
||||
path: 'compose',
|
||||
component: ComposeMessageComponent,
|
||||
outlet: 'modal'
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
loadChildren: 'app/admin/admin.module#AdminModule',
|
||||
|
|
|
@ -8,11 +8,16 @@ import {
|
|||
// #docregion preload-v1
|
||||
} from '@angular/router';
|
||||
|
||||
import { PageNotFoundComponent } from './not-found.component';
|
||||
import { ComposeMessageComponent } from './compose-message.component';
|
||||
import { CanDeactivateGuard } from './can-deactivate-guard.service';
|
||||
import { AuthGuard } from './auth-guard.service';
|
||||
|
||||
const appRoutes: Routes = [
|
||||
{
|
||||
path: 'compose',
|
||||
component: ComposeMessageComponent,
|
||||
outlet: 'modal'
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
loadChildren: 'app/admin/admin.module#AdminModule',
|
||||
|
|
|
@ -3,12 +3,17 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
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 { AuthGuard } from './auth-guard.service';
|
||||
import { PreloadSelectedModules } from './selective-preload-strategy';
|
||||
|
||||
const appRoutes: Routes = [
|
||||
{
|
||||
path: 'compose',
|
||||
component: ComposeMessageComponent,
|
||||
outlet: 'modal'
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
loadChildren: 'app/admin/admin.module#AdminModule',
|
||||
|
@ -26,10 +31,6 @@ const appRoutes: Routes = [
|
|||
data: {
|
||||
preload: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
component: PageNotFoundComponent
|
||||
}
|
||||
// #enddocregion preload-v2
|
||||
];
|
||||
|
|
|
@ -12,6 +12,9 @@ import { Component } from '@angular/core';
|
|||
<a routerLink="/admin" routerLinkActive="active">Admin</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
// #enddocregion template
|
||||
<router-outlet name="modal"></router-outlet>
|
||||
// #enddocregion template
|
||||
`
|
||||
// #enddocregion template
|
||||
})
|
||||
|
|
|
@ -12,8 +12,10 @@ import { Component } from '@angular/core';
|
|||
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
|
||||
<a routerLink="/admin" routerLinkActive="active">Admin</a>
|
||||
<a routerLink="/login" routerLinkActive="active">Login</a>
|
||||
<a [routerLink]="[{ outlets: { modal: ['compose'] } }]">Contact</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
<router-outlet name="modal"></router-outlet>
|
||||
`
|
||||
// #enddocregion template
|
||||
})
|
||||
|
|
|
@ -6,13 +6,13 @@ import { CommonModule } from '@angular/common';
|
|||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { PageNotFoundComponent }from './not-found.component';
|
||||
import { PageNotFoundComponent } from './not-found.component';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
||||
import { HeroesModule } from './heroes/heroes.module';
|
||||
// #docregion crisis-center-module
|
||||
import { CrisisCenterModule } from './crisis-center/crisis-center.module';
|
||||
// #enddocregion crisis-center-module
|
||||
import { ComposeMessageComponent } from './compose-message.component';
|
||||
// #docregion admin-module
|
||||
import { AdminModule } from './admin/admin.module';
|
||||
// #docregion crisis-center-module
|
||||
|
@ -26,13 +26,16 @@ import { DialogService } from './dialog.service';
|
|||
HeroesModule,
|
||||
CrisisCenterModule,
|
||||
// #enddocregion crisis-center-module
|
||||
// #enddocregion admin-module
|
||||
AdminModule,
|
||||
// #docregion crisis-center-module
|
||||
AppRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
PageNotFoundComponent
|
||||
// #enddocregion admin-module, crisis-center-module
|
||||
ComposeMessageComponent
|
||||
// #docregion admin-module, crisis-center-module
|
||||
],
|
||||
providers: [
|
||||
DialogService
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
// #docplaster
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { PageNotFoundComponent }from './not-found.component';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
||||
import { HeroesModule } from './heroes/heroes.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';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
HeroesModule,
|
||||
CrisisCenterModule,
|
||||
|
@ -24,7 +25,7 @@ import { DialogService } from './dialog.service';
|
|||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
PageNotFoundComponent
|
||||
ComposeMessageComponent
|
||||
],
|
||||
providers: [
|
||||
DialogService
|
||||
|
@ -33,3 +34,4 @@ import { DialogService } from './dialog.service';
|
|||
})
|
||||
export class AppModule {
|
||||
}
|
||||
// #enddocregion
|
||||
|
|
|
@ -9,6 +9,8 @@ import { AppRoutingModule } from './app-routing.module';
|
|||
|
||||
import { HeroesModule } from './heroes/heroes.module';
|
||||
import { CrisisCenterModule } from './crisis-center/crisis-center.module';
|
||||
import { ComposeMessageComponent } from './compose-message.component';
|
||||
|
||||
import { LoginRoutingModule } from './login-routing.module';
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
|
@ -25,7 +27,7 @@ import { DialogService } from './dialog.service';
|
|||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
PageNotFoundComponent,
|
||||
ComposeMessageComponent,
|
||||
LoginComponent
|
||||
],
|
||||
providers: [
|
||||
|
|
|
@ -8,6 +8,7 @@ import { PageNotFoundComponent } from './not-found.component';
|
|||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
||||
import { HeroesModule } from './heroes/heroes.module';
|
||||
import { ComposeMessageComponent } from './compose-message.component';
|
||||
import { LoginRoutingModule } from './login-routing.module';
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
|
@ -23,7 +24,7 @@ import { DialogService } from './dialog.service';
|
|||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
PageNotFoundComponent,
|
||||
ComposeMessageComponent,
|
||||
LoginComponent
|
||||
],
|
||||
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)
|
||||
* add [child routes](#child-routing-component) under a feature section
|
||||
* [grouping child routes](#component-less-route) without a component
|
||||
* displaying [multiple routes](#named-outlets) in separate outlets
|
||||
* [redirecting](#redirect) from one route to another
|
||||
* confirming or canceling navigation with [guards](#guards)
|
||||
* [CanActivate](#can-activate-guard) to prevent navigation to a route
|
||||
|
@ -473,7 +474,7 @@ a#router-outlet
|
|||
.l-sub-section
|
||||
:marked
|
||||
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
|
||||
: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/heroes.module.ts,
|
||||
router/ts/app/heroes/heroes-routing.module.ts`,
|
||||
null,
|
||||
'null,null,v3,null,null,null,null,null',
|
||||
`app.component.ts,
|
||||
app.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 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
|
||||
any other feature's component.
|
||||
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
|
||||
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
|
||||
: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')
|
||||
|
||||
<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>
|
||||
.l-main-section
|
||||
h2#guards Route Guards
|
||||
|
|
Loading…
Reference in New Issue