From 397c337378c6e82a4428a26063def33b911cc5dd Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 20 Dec 2016 17:26:09 -0600 Subject: [PATCH] docs(router): Added section for named outlets and secondary routes (#2842) --- public/docs/_examples/router/e2e-spec.ts | 18 +-- .../router/ts/app/app-routing.module.3.ts | 14 ++- .../router/ts/app/app-routing.module.4.ts | 10 +- .../router/ts/app/app-routing.module.5.ts | 12 +- .../router/ts/app/app-routing.module.6.ts | 11 +- .../router/ts/app/app-routing.module.ts | 17 +-- .../router/ts/app/app.component.4.ts | 3 + .../_examples/router/ts/app/app.component.ts | 2 + .../_examples/router/ts/app/app.module.4.ts | 21 ++-- .../_examples/router/ts/app/app.module.5.ts | 22 ++-- .../_examples/router/ts/app/app.module.7.ts | 8 +- .../_examples/router/ts/app/app.module.ts | 9 +- .../ts/app/compose-message.component.1.ts | 108 ++++++++++++++++++ .../ts/app/compose-message.component.ts | 105 +++++++++++++++++ public/docs/ts/latest/guide/router.jade | 106 ++++++++++++++++- 15 files changed, 409 insertions(+), 57 deletions(-) create mode 100644 public/docs/_examples/router/ts/app/compose-message.component.1.ts create mode 100644 public/docs/_examples/router/ts/app/compose-message.component.ts diff --git a/public/docs/_examples/router/e2e-spec.ts b/public/docs/_examples/router/e2e-spec.ts index f0c79e2dfc..00a9d2d924 100644 --- a/public/docs/_examples/router/e2e-spec.ts +++ b/public/docs/_examples/router/e2e-spec.ts @@ -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'); }); }); diff --git a/public/docs/_examples/router/ts/app/app-routing.module.3.ts b/public/docs/_examples/router/ts/app/app-routing.module.3.ts index 16d9d4ae59..431d7ee32a 100644 --- a/public/docs/_examples/router/ts/app/app-routing.module.3.ts +++ b/public/docs/_examples/router/ts/app/app-routing.module.3.ts @@ -1,11 +1,17 @@ // #docplaster -// #docregion -import { NgModule } from '@angular/core'; +// #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({ diff --git a/public/docs/_examples/router/ts/app/app-routing.module.4.ts b/public/docs/_examples/router/ts/app/app-routing.module.4.ts index e5da4acdd1..a2a3ecb8c5 100644 --- a/public/docs/_examples/router/ts/app/app-routing.module.4.ts +++ b/public/docs/_examples/router/ts/app/app-routing.module.4.ts @@ -1,13 +1,17 @@ -// #docplaster // #docregion import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { PageNotFoundComponent }from './not-found.component'; -import { CanDeactivateGuard } from './can-deactivate-guard.service'; +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({ diff --git a/public/docs/_examples/router/ts/app/app-routing.module.5.ts b/public/docs/_examples/router/ts/app/app-routing.module.5.ts index 5791c4c4e0..30b808f051 100644 --- a/public/docs/_examples/router/ts/app/app-routing.module.5.ts +++ b/public/docs/_examples/router/ts/app/app-routing.module.5.ts @@ -4,15 +4,21 @@ import { NgModule } from '@angular/core'; // #docregion import-router import { RouterModule, Routes } from '@angular/router'; // #enddocregion import-router -import { PageNotFoundComponent } from './not-found.component'; -import { CanDeactivateGuard } from './can-deactivate-guard.service'; + +import { ComposeMessageComponent } from './compose-message.component'; +import { CanDeactivateGuard } from './can-deactivate-guard.service'; // #docregion can-load-guard -import { AuthGuard } from './auth-guard.service'; +import { AuthGuard } from './auth-guard.service'; // #enddocregion can-load-guard // #docregion lazy-load-admin, can-load-guard const appRoutes: Routes = [ + { + path: 'compose', + component: ComposeMessageComponent, + outlet: 'modal' + }, { path: 'admin', loadChildren: 'app/admin/admin.module#AdminModule', diff --git a/public/docs/_examples/router/ts/app/app-routing.module.6.ts b/public/docs/_examples/router/ts/app/app-routing.module.6.ts index e44382979a..3fb6cc4e85 100644 --- a/public/docs/_examples/router/ts/app/app-routing.module.6.ts +++ b/public/docs/_examples/router/ts/app/app-routing.module.6.ts @@ -8,11 +8,16 @@ import { // #docregion preload-v1 } from '@angular/router'; -import { PageNotFoundComponent } from './not-found.component'; -import { CanDeactivateGuard } from './can-deactivate-guard.service'; -import { AuthGuard } from './auth-guard.service'; +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', diff --git a/public/docs/_examples/router/ts/app/app-routing.module.ts b/public/docs/_examples/router/ts/app/app-routing.module.ts index 119d92aecd..f4588738aa 100644 --- a/public/docs/_examples/router/ts/app/app-routing.module.ts +++ b/public/docs/_examples/router/ts/app/app-routing.module.ts @@ -3,12 +3,17 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { PageNotFoundComponent } from './not-found.component'; -import { CanDeactivateGuard } from './can-deactivate-guard.service'; -import { AuthGuard } from './auth-guard.service'; -import { PreloadSelectedModules } from './selective-preload-strategy'; +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 ]; diff --git a/public/docs/_examples/router/ts/app/app.component.4.ts b/public/docs/_examples/router/ts/app/app.component.4.ts index 9769d4f2da..d6352ea508 100644 --- a/public/docs/_examples/router/ts/app/app.component.4.ts +++ b/public/docs/_examples/router/ts/app/app.component.4.ts @@ -12,6 +12,9 @@ import { Component } from '@angular/core'; Admin + // #enddocregion template + + // #enddocregion template ` // #enddocregion template }) diff --git a/public/docs/_examples/router/ts/app/app.component.ts b/public/docs/_examples/router/ts/app/app.component.ts index 37f69472a0..142576a7e3 100644 --- a/public/docs/_examples/router/ts/app/app.component.ts +++ b/public/docs/_examples/router/ts/app/app.component.ts @@ -12,8 +12,10 @@ import { Component } from '@angular/core'; Heroes Admin Login + Contact + ` // #enddocregion template }) diff --git a/public/docs/_examples/router/ts/app/app.module.4.ts b/public/docs/_examples/router/ts/app/app.module.4.ts index 9a51024775..8ae77ac07d 100644 --- a/public/docs/_examples/router/ts/app/app.module.4.ts +++ b/public/docs/_examples/router/ts/app/app.module.4.ts @@ -5,19 +5,19 @@ import { NgModule } from '@angular/core'; 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 { AppComponent } from './app.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'; +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'; +import { AdminModule } from './admin/admin.module'; // #docregion crisis-center-module -import { DialogService } from './dialog.service'; +import { DialogService } from './dialog.service'; @NgModule({ imports: [ @@ -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 diff --git a/public/docs/_examples/router/ts/app/app.module.5.ts b/public/docs/_examples/router/ts/app/app.module.5.ts index c28656005b..d8396c5038 100644 --- a/public/docs/_examples/router/ts/app/app.module.5.ts +++ b/public/docs/_examples/router/ts/app/app.module.5.ts @@ -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 { AppComponent } from './app.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 { HeroesModule } from './heroes/heroes.module'; +import { CrisisCenterModule } from './crisis-center/crisis-center.module'; +import { ComposeMessageComponent } from './compose-message.component'; -import { DialogService } from './dialog.service'; +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 diff --git a/public/docs/_examples/router/ts/app/app.module.7.ts b/public/docs/_examples/router/ts/app/app.module.7.ts index e38d4dc597..ba766758d5 100644 --- a/public/docs/_examples/router/ts/app/app.module.7.ts +++ b/public/docs/_examples/router/ts/app/app.module.7.ts @@ -7,8 +7,10 @@ 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 { 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: [ diff --git a/public/docs/_examples/router/ts/app/app.module.ts b/public/docs/_examples/router/ts/app/app.module.ts index 7f710fad1f..b580c10618 100644 --- a/public/docs/_examples/router/ts/app/app.module.ts +++ b/public/docs/_examples/router/ts/app/app.module.ts @@ -7,9 +7,10 @@ import { AppComponent } from './app.component'; import { PageNotFoundComponent } from './not-found.component'; import { AppRoutingModule } from './app-routing.module'; -import { HeroesModule } from './heroes/heroes.module'; -import { LoginRoutingModule } from './login-routing.module'; -import { LoginComponent } from './login.component'; +import { HeroesModule } from './heroes/heroes.module'; +import { ComposeMessageComponent } from './compose-message.component'; +import { LoginRoutingModule } from './login-routing.module'; +import { LoginComponent } from './login.component'; import { DialogService } from './dialog.service'; @@ -23,7 +24,7 @@ import { DialogService } from './dialog.service'; ], declarations: [ AppComponent, - PageNotFoundComponent, + ComposeMessageComponent, LoginComponent ], providers: [ diff --git a/public/docs/_examples/router/ts/app/compose-message.component.1.ts b/public/docs/_examples/router/ts/app/compose-message.component.1.ts new file mode 100644 index 0000000000..539c6fe5d6 --- /dev/null +++ b/public/docs/_examples/router/ts/app/compose-message.component.1.ts @@ -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: ` +

Contact Crisis Center

+
+ {{ details }} +
+
+
+ +
+
+ +
+
+

+ +// #enddocregion v1 + +// #docregion v1 +

+ `, + 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 diff --git a/public/docs/_examples/router/ts/app/compose-message.component.ts b/public/docs/_examples/router/ts/app/compose-message.component.ts new file mode 100644 index 0000000000..57596e5cc3 --- /dev/null +++ b/public/docs/_examples/router/ts/app/compose-message.component.ts @@ -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: ` +

Contact Crisis Center

+
+ {{ details }} +
+
+
+ +
+
+ +
+
+

+ + +

+ `, + 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(); + } +} diff --git a/public/docs/ts/latest/guide/router.jade b/public/docs/ts/latest/guide/router.jade index f2495494d5..a97bafb510 100644 --- a/public/docs/ts/latest/guide/router.jade +++ b/public/docs/ts/latest/guide/router.jade @@ -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*** ``. - 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') + +.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 Secondary Route Navigation: 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)', '') + .l-main-section h2#guards Route Guards