发表于: 2019-03-23 21:02:31

1 558


今天完成的事:

把危机中心模块导入到 AppModule 的路由中

就像 HeroesModule 模块中一样,你必须把 CrisisCenterModule 添加到 AppModule 的 imports 数组中,就在 AppRoutingModule 前面:

  1. import { NgModule }       from '@angular/core';
  2. import { FormsModule }    from '@angular/forms';
  3. import { CommonModule }   from '@angular/common';
  4.  
  5. import { CrisisCenterHomeComponent } from './crisis-center-home/crisis-center-home.component';
  6. import { CrisisListComponent }       from './crisis-list/crisis-list.component';
  7. import { CrisisCenterComponent }     from './crisis-center/crisis-center.component';
  8. import { CrisisDetailComponent }     from './crisis-detail/crisis-detail.component';
  9.  
  10. import { CrisisCenterRoutingModule } from './crisis-center-routing.module';
  11.  
  12. @NgModule({
  13.  imports: [
  14.    CommonModule,
  15.    FormsModule,
  16.    CrisisCenterRoutingModule
  17.  ],
  18.  declarations: [
  19.    CrisisCenterComponent,
  20.    CrisisListComponent,
  21.    CrisisCenterHomeComponent,
  22.    CrisisDetailComponent
  23.  ]
  24. })
  25. export class CrisisCenterModule {}

从 app.routing.ts 中移除危机中心的初始路由。 这些特性路由现在是由 HeroesModule 和 CrisisCenter 特性模块提供的。

app-routing.module.ts 文件中只有应用的顶级路由,比如默认路由和通配符路由。

src/app/app-routing.module.ts (v3)
import { NgModule }                from '@angular/core';import { RouterModule, Routes }    from '@angular/router';import { PageNotFoundComponent }  from './page-not-found/page-not-found.component';const appRoutes: Routes = [  { path: '',   redirectTo: '/heroes', pathMatch: 'full' },  { path: '**', component: PageNotFoundComponent }];@NgModule({  imports: [    RouterModule.forRoot(      appRoutes,      { enableTracing:true } // <-- debugging purposes only    )  ],  exports: [    RouterModule  ]})export class AppRoutingModule {}

相对导航

虽然构建出了危机中心特性区,你却仍在使用以斜杠开头的绝对路径来导航到危机详情的路由。

路由器会从路由配置的顶层来匹配像这样的绝对路径

你固然可以继续像危机中心特性区一样使用绝对路径,但是那样会把链接钉死在特定的父路由结构上。 如果你修改了父路径 /crisis-center,那就不得不修改每一个链接参数数组。

通过改成定义相对于当前 URL 的路径,你可以把链接从这种依赖中解放出来。 当你修改了该特性区的父路由路径时,该特性区内部的导航仍然完好无损。

例子如下:

路由器支持在链接参数数组中使用“目录式”语法来为查询路由名提供帮助:

./ 或 无前导斜线 形式是相对于当前级别的。

../ 会回到当前路由路径的上一级。

你可以把相对导航语法和一个祖先路径组合起来用。 如果不得不导航到一个兄弟路由,你可以用 ../<sibling> 来回到上一级,然后进入兄弟路由路径中。

用 Router.navigate 方法导航到相对路径时,你必须提供当前的 ActivatedRoute,来让路由器知道你现在位于路由树中的什么位置。

链接参数数组后面,添加一个带有 relativeTo 属性的对象,并把它设置为当前的 ActivatedRoute。 这样路由器就会基于当前激活路由的位置来计算出目标 URL。

当调用路由器的 navigateByUrl 时,总是要指定完整的绝对路径

你已经注入过了 ActivatedRoute,你需要把它来和相对导航路径组合在一起。

如果用 RouterLink 来代替 Router 服务进行导航,就要使用相同的链接参数数组,不过不再需要提供 relativeTo 属性。 ActivatedRoute 已经隐含在了 RouterLink 指令中。

修改 CrisisDetailComponent 的 gotoCrises 方法,来使用相对路径返回危机中心列表。

src/app/crisis-center/crisis-detail/crisis-detail.component.ts (relative navigation)
// Relative navigation back to the crisesthis.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.route });

注意这个路径使用了 ../ 语法返回上一级。 如果当前危机的 id 是 3,那么最终返回到的路径就是 /crisis-center/;id=3;foo=foo

用命名出口(outlet)显示多重路由

你决定给用户提供一种方式来联系危机中心。 当用户点击“Contact”按钮时,你要在一个弹出框中显示一条消息。

即使在应用中的不同页面之间切换,这个弹出框也应该始终保持打开状态,直到用户发送了消息或者手动取消。 显然,你不能把这个弹出框跟其它放到页面放到同一个路由出口中。

迄今为止,你只定义过单路由出口,并且在其中嵌套了子路由以便对路由分组。 在每个模板中,路由器只能支持一个无名主路由出口。

模板还可以有多个命名的路由出口。 每个命名出口都自己有一组带组件的路由。 多重出口可以在同一时间根据不同的路由来显示不同的内容。

在 AppComponent 中添加一个名叫“popup”的出口,就在无名出口的下方。

src/app/app.component.html (outlets)
<div [@routeAnimation]="getAnimationData(routerOutlet)">  <router-outlet #routerOutlet="outlet"></router-outlet></div><router-outlet name="popup"></router-outlet>

一旦你学会了如何把一个弹出框组件路由到该出口,那里就是将会出现弹出框的地方。

第二路由

命名出口是第二路由的目标。

第二路由很像主路由,配置方式也一样。它们只有一些关键的不同点:

  • 它们彼此互不依赖。

  • 它们与其它路由组合使用。

  • 它们显示在命名出口中。

生成一个新的组件来组合这个消息。

ng generate component compose-message

它显示一个简单的表单,包括一个头、一个消息输入框和两个按钮:“Send”和“Cancel”。

Contact popup

下面是该组件及其模板和样式:

:host {  position: relative; bottom: 10%;}

它看起来几乎和你以前见过其它组件一样,但有两个值得注意的区别。

注意,send() 方法在发送消息和关闭弹出框之前通过等待模拟了一秒钟的延迟。

closePopup() 方法用把 popup 出口导航到 null 的方式关闭了弹出框。 这个奇怪的用法在稍后的部分有讲解。

添加第二路由

打开 AppRoutingModule,并把一个新的 compose 路由添加到 appRoutes 中。

src/app/app-routing.module.ts (compose route)
{  path: 'compose',  component: ComposeMessageComponent,  outlet: 'popup'},

对 path 和 component 属性应该很熟悉了吧。 注意这个新的属性 outlet 被设置成了 'popup'。 这个路由现在指向了 popup 出口,而 ComposeMessageComponent 也将显示在那里。

用户需要某种途径来打开这个弹出框。 打开 AppComponent,并添加一个“Contact”链接。

src/app/app.component.html (contact-link)
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>

虽然 compose 路由被钉死在了 popup 出口上,但这仍然不足以向 RouterLink 指令表明要加载该路由。 你还要在链接参数数组中指定这个命名出口,并通过属性绑定的形式把它绑定到 RouterLink 上。

链接参数数组包含一个只有一个 outlets 属性的对象,它的值是另一个对象,这个对象以一个或多个路由的出口名作为属性名。 在这里,它只有一个出口名“popup”,它的值则是另一个链接参数数组,用于指定 compose 路由。

意思是,当用户点击此链接时,在路由出口 popup 中显示与 compose 路由相关联的组件。

当只需要考虑一个路由和一个无名出口时,外部对象中的这个 outlets 对象是完全不必要的。

路由器假设这个路由指向了无名的主出口,并为你创建这些对象。

路由到一个命名出口就会揭示一个以前被隐藏的真相: 你可以在同一个 RouterLink 指令中为多个路由出口指定多个路由。

这里你实际上没能这样做。要想指向命名出口,你就得使用一种更强大也更啰嗦的语法。

第二路由导航:在导航期间合并路由

导航到危机中心并点击“Contact”,你将会在浏览器的地址栏看到如下 URL:

http://.../crisis-center(popup:compose)

这个 URL 中有意思的部分是 ... 后面的这些:

  • crisis-center 是主导航。

  • 圆括号包裹的部分是第二路由。

  • 第二路由包括一个出口名称(popup)、一个冒号分隔符和第二路由的路径(compose)。

点击 Heroes 链接,并再次查看 URL:

http://.../heroes(popup:compose)

主导航的部分变化了,而第二路由没有变。

路由器在导航树中对两个独立的分支保持追踪,并在 URL 中对这棵树进行表达。

你还可以添加更多出口和更多路由(无论是在顶层还是在嵌套的子层)来创建一个带有多个分支的导航树。 路由器将会生成相应的 URL。

通过像前面那样填充 outlets 对象,你可以告诉路由器立即导航到一棵完整的树。 然后把这个对象通过一个链接参数数组传给 router.navigate 方法。

有空的时候你可以自行试验这些可能性。

清除第二路由

正如你刚刚学到的,除非导航到新的组件,否则路由出口中的组件会始终存在。 这里涉及到的第二出口也同样如此。

每个第二出口都有自己独立的导航,跟主出口的导航彼此独立。 修改主出口中的当前路由并不会影响到 popup 出口中的。 这就是为什么在危机中心和英雄管理之间导航时,弹出框始终都是可见的。

点击“send”或“cancel”按钮,则清除弹出框视图。 为何如此?再看看 closePopup() 方法:

src/app/compose-message/compose-message.component.ts (closePopup)
closePopup() {  // Providing a `null` value to the named outlet  // clears the contents of the named outlet  this.router.navigate([{ outlets: { popup: null }}]);}

它使用 Router.navigate() 方法进行强制导航,并传入了一个链接参数数组

就像在 AppComponent 中绑定到的 Contact RouterLink 一样,它也包含了一个带 outlets 属性的对象。 outlets 属性的值是另一个对象,该对象用一些出口名称作为属性名。 唯一的命名出口是 'popup'

但这次,'popup' 的值是 nullnull 不是一个路由,但却是一个合法的值。 把 popup 这个 RouterOutlet 设置为 null 会清除该出口,并且从当前 URL 中移除第二路由 popup

里程碑 5:路由守卫

现在,任何用户都能在任何时候导航到任何地方。 但有时候这样是不对的。

  • 该用户可能无权导航到目标组件。

  • 可能用户得先登录(认证)。

  • 在显示目标组件前,你可能得先获取某些数据。

  • 在离开组件前,你可能要先保存修改。

  • 你可能要询问用户:你是否要放弃本次更改,而不用保存它们?

你可以往路由配置中添加守卫,来处理这些场景。

守卫返回一个值,以控制路由器的行为:

  • 如果它返回 true,导航过程会继续

  • 如果它返回 false,导航过程就会终止,且用户留在原地。

  • 如果它返回 UrlTree,则取消当前的导航,并且开始导航到返回的这个 UrlTree.

注意:守卫还可以告诉路由器导航到别处,这样也会取消当前的导航。要想在守卫中这么做,就要返回 false

守卫可以用同步的方式返回一个布尔值。但在很多情况下,守卫无法用同步的方式给出答案。 守卫可能会向用户问一个问题、把更改保存到服务器,或者获取新数据,而这些都是异步操作。

因此,路由的守卫可以返回一个 Observable<boolean> 或 Promise<boolean>,并且路由器会等待这个可观察对象被解析为 true 或 false

注意: 提供给路由器的可观察对象还必须能结束(complete)。否则,导航就不会继续。

路由器可以支持多种守卫接口:

  • CanActivate来处理导航某路由的情况。

  • CanActivateChild来处理导航某子路由的情况。

  • CanDeactivate来处理从当前路由离开的情况.

  • Resolve在路由激活之前获取路由数据。

  • CanLoad来处理异步导航到某特性模块的情况。

在分层路由的每个级别上,你都可以设置多个守卫。 路由器会先按照从最深的子路由由下往上检查的顺序来检查 CanDeactivate() 和 CanActivateChild() 守卫。 然后它会按照从上到下的顺序检查 CanActivate() 守卫。 如果特性模块是异步加载的,在加载它之前还会检查 CanLoad() 守卫。 如果任何一个守卫返回 false,其它尚未完成的守卫会被取消,这样整个导航就被取消了。

接下来的小节中有一些例子。

CanActivate: 要求认证

应用程序通常会根据访问者来决定是否授予某个特性区的访问权。 你可以只对已认证过的用户或具有特定角色的用户授予访问权,还可以阻止或限制用户访问权,直到用户账户激活为止。

CanActivate 守卫是一个管理这些导航类业务规则的工具。

添加一个“管理”特性模块

在下一节,你将会使用一些新的管理特性来扩展危机中心。 那些特性尚未定义,但是你可以先从添加一个名叫 AdminModule 的特性模块开始。

生成一个带有特性模块文件和路由配置文件的 admin 目录。

ng generate module admin --routing

接下来,生成一些支持性组件。

ng generate component admin/admin-dashboard

ng generate component admin/admin
ng generate component admin/manage-crises
ng generate component admin/manage-heroes

管理特性区的文件是这样的:

src/app/admin

admin

admin.component.css
admin.component.html
admin.component.ts
admin-dashboard
admin-dashboard.component.css
admin-dashboard.component.html
admin-dashboard.component.ts

manage-crises

manage-crises.component.css

manage-crises.component.html

manage-crises.component.ts

manage-heroes

manage-heroes.component.css
manage-heroes.component.html
manage-heroes.component.ts
admin.module.ts
admin-routing.module.ts

管理特性模块包含 AdminComponent,它用于在特性模块内的仪表盘路由以及两个尚未完成的用于管理危机和英雄的组件之间进行路由。

<h3>ADMIN</h3><nav>  <a routerLink="./" routerLinkActive="active"    [routerLinkActiveOptions]="{ exact: true }">Dashboard</a>  <a routerLink="./crises" routerLinkActive="active">Manage Crises</a>  <a routerLink="./heroes" routerLinkActive="active">Manage Heroes</a></nav><router-outlet></router-outlet>

虽然管理仪表盘中的 RouterLink 只包含一个没有其它 URL 段的斜杠 /,但它能匹配管理特性区下的任何路由。 但你只希望在访问 Dashboard 路由时才激活该链接。 往 Dashboard 这个 routerLink 上添加另一个绑定 [routerLinkActiveOptions]="{ exact: true }", 这样就只有当用户导航到 /admin 这个 URL 时才会激活它,而不会在导航到它的某个子路由时。



目标:

继续学习

问题:

进度慢

收获:

加油





返回列表 返回列表
评论

    分享到