路由配置
最简单,一个配置项包含了 path
和 component
两个属性
创建根路由模块
需要注意的是,根路由模块默认提供的路由策略为 PathLocationStrategy
。该策略要求应用必须设置一个 base
路径,用于作为前缀来生成和解析 URL 。设置 base 路径最简单的方式是在 index.html
文件中设置 <base>
元素的 href 属性。路由策略将会在下一节进行详细介绍。
路由策略
Angular 提供了 PathLocationStrategy 和 HashLocationStrategy 两种路由策略,分别表示使用 URL 的 path 部分和 hash 部分来进行路由匹配。
区别:
PathLocationStrategy 每次请求服务端,HashLocationStrategy 更改 URL 的 hash 部分不请求服务端。
PathLocationStrategy
PathLocationStrategy 使用 URL 的 path 部分来进行路由匹配,因此与 HashLocationStrategy 的不同之处在于,浏览器会将配置项对应的 URL 原封不动地发送给服务器
要使用 PathLocationStrategy
路由策略,必须满足三个条件:
第一,浏览器需要支持 HTML 5 的 history.pushState()
方法,正是这一方法使得 RouterLink 指令在跳转时即使更改了 URL 的 path 部分,也依然不会引起页面刷新。
第二,需要在服务器上进行设置,将应用的所有 URL 重定向到应用的首页。这是因为该策略所生成的 URL (如 http://localhost:3000/list
)在服务器上并不存在与其相对应的文件结构,如果不进行重定向,服务器将返回 404 错误。
第三,需要为应用设置一个 base 路径, Angular 将以 base 路径为前缀来生成和解析 URL。这样做的好处是服务器可以根据 base 路径来区分来自不同应用的请求。如何在服务器上进行重定向设置超出了 Angular 的范畴,这里就不深入讲解了,接 下来只对设置 base 路径的两种方式加以介绍。
HashLocationStrategy
其原理是利用了浏览器在处理 hash 部分的两个特性。
第一,浏览器向服务器发送请求时不会带上 hash 部分的内容。如图 11-4 所示,如 果通讯录采用了 HashLocationStrategy,那么对于其所有配置项所对应的 URL,浏览器向 服务器发送的请求都为同一个,服务器只需要返回应用首页即可, Angular 在获取首页 后会根据 hash 的内容来匹配路由配置项并渲染相应的组件。
第二,更改 URL 的 hash 部分不会向服务器重新发送请求,这使得在进行跳转的时候不会引发页面的刷新和应用的重新加载。
可以在根模块的 RouterModule.forRoot() 的第二个参数中传入一个带有 useHash: true
的对象,以回到基于 HashLocationStrategy 的传统方式。
@NgModule(
imports: [RouterModule.forRoot(rootRouterConfig, {useHash: true})],
// ...
})
路由跳转
使用指令跳转
RouterLink
指令跳转通过使用 RouterLink 指令来完成。该指令接收一个链接参数数组, Angular 将根据该数组来生成 UrlTree 实例进行跳转。
ts
import { ContentComponent } from './share/content/content.component';
import { ListComponent } from './share/list/list.component';
const rootRouterConfig: Routes = [
{ path:'', component: ContentComponent },
{ path:'list', component: ListComponent },
{ path:'content', component: ContentComponent },
];
html
<nav>
<!-- http://localhost:4200/collection -->
<a [routerLink]="['/content']">
<i>content</i>
</a>
<!-- http://localhost:4200/list -->
<a [routerLink]="['/list']">
<i>list</i>
</a>
</nav>
直接 href 的结果
如果不借助于 RouterLink 指令而以纯 HTML 的方式来定义超链接,所导致的结果是单击超链接后会使得整个页面被重新加载
。
<nav>
<a href="/collection">
<i>content</i>
</a>
<a href="/list">
<i>list</i>
</a>
</nav>
RouterLink 不重新加载原因
Angular 通过以下两个步骤来保证在不重新加载应用的情况下完成跳转。
-
在 click 事件中调用 preventDefault() 方法来禁止单击
<a>
标签后向服务器发送请求的行为,从而避免了跳转加载。 -
调用 Router.navigateByUrl() 方法来启动跳转流程。
@HostListener('click', ['$event.button', '$event.ctrlKey', '$event.metaKey'])
onClick(button: number, ctrlKey: boolean, metaKey: boolean): boolean {
//...
this.router.navigateByUrl(this.urlTree); // 跳转到指定页面,渲染相应组件
return false; // 当 HostListener 装饰的回调函数返回 false 时,
// Angular 会调用 `preventDefault()` 方法
}
不依赖超链接 a 标签
RouterLink 指令的另一个强大之处在于它可以被应用在任何 HTML 元素上,使得页面跳转不需要依赖超链接。
<button [routerLink]="['/list']">
<i>list</i>
</button>
RouterLinkActive 指令
当 RouterLink 被激活时,还可以通过 RouterLinkActive
指令为其相应的 HTML元素指定 CSS 类。
下面的例子定义了一个 CSS 类 .active
,并通过 routerLinkActive 将其赋给 list
的链接。当单击该链接后, .active 类将被应用到 <a>
标签上。
<nav>
<!-- http://localhost:4200/collection -->
<a [routerLink]="['/content']" routerLinkActive="active">
<i>content</i>
</a>
<!-- http://localhost:4200/list -->
<a [routerLink]="['/list']" routerLinkActive="active">
<i>list</i>
</a>
</nav>
使用代码跳转
跳转流程是通过调用 Router.navigateByUrl()
方法来启动的。 RouterLink 指令仅响应 click 事件,如果需要响应其他事件或者需要根据运行时的数据动态决定如何跳转,则可以通过调用 Router.navigateByUrl() 或其兄弟方法 Router.navigate()
来完成。
setTimeout(()=>{
_router.navigateByUrl('/content');
// 或者执行: _router.navigate(['/content']);
}, 1000);
还支持用 extras 参数定义跳转的具体行为。例如,如果想在不改变 URL 的情况下完成跳转
_router.navigateByUrl('/collection', {skipLocationChange: true});
基于所提供的 URL 进行导航,必须使用绝对路径。See also: 路由和导航指南
navigateByUrl(url: string | UrlTree, extras: NavigationBehaviorOptions = { skipLocationChange: false }): Promise<boolean>
参数
url
string | UrlTree 一个绝对 URL。该函数不会对当前 URL 做任何修改。 extras
NavigationBehaviorOptions 一个包含一组属性的对象,它会修改导航策略。 该函数会忽略 NavigationExtras 中任何可能会改变所提供的 URL 的属性可选值。默认值为 { skipLocationChange: false }
。返回值
一个 Promise,当导航成功时,它会解析成
true
;导航失败或出错时,它会解析成false
。
获取路由信息
要从路由中获取信息:
- 把 ActivatedRoute 和 ParamMap 导入你的组件。
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
这些 import
语句添加了组件所需的几个重要元素。要详细了解每个 API,请参阅以下 API 页面:
- 通过把 ActivatedRoute 的一个实例添加到你的应用的构造函数中来注入它:
constructor(
private route: ActivatedRoute,
) {}
- 更新
ngOnInit()
方法来访问这个 ActivatedRoute 并跟踪name
参数:
ngOnInit() {
this.route.queryParams.subscribe(params => {
this.name = params['name'];
});
}
注意:
前面的例子使用了一个变量 name
,并根据 name
参数给它赋值。
路由器状态
每个成功的导航生命周期结束后,路由器都会构建一个 ActivatedRoute 对象树,它构成了路由器的当前状态。你可以从任何地方使用应用的 Router 服务和 routerState
属性来访问当前的 RouterState。
RouterState 中的每个 ActivatedRoute 都提供了向上或向下遍历路由树的方法,用于从父路由、子路由和兄弟路由中获取信息。
路由参数
Path 参数
在定义一个配置项的 path 属性时,可以使用“/
”字符来对 path 属性进行分段,如果一个分段以 “:
” 字符开头,则 URL 中与该分段进行匹配的部分将作为参数传递到组件中。
下面的代码为联系人详情页的路由配置项,其定义了一个名为 id 的 Path 参数,对于 http://localhost:3000/detail/1
,参数 id 的值为 1;对于 http://localhost:3000/detail/2
,参数 id 的值则 为 2;依此类推。
export const ContactsAppRouters: RouterConfig = [
{ path: 'detail/:id', component: DetailComponent }
];
只有当 URL 解析出来的分段数和 path 属性的分段数一致时,才能匹配到该配置项
http://localhost:3000/detail
#分段数为1
http://localhost:3000/detail/1/segment
#分段数为3
给路由参数赋值,除可以直接在浏览器地址栏中输入 URL 外,还可以通过 RouterLink 指令或者跳转方法来完成:
// Angular 会将链接参数数组的每一个非对象元素当成一个分段进行拼接,
// 因此下面的链接参数数组对应的 path 为 `detail/1`
<a [routerLink]="['/detail', 1]">
_router.navigate(['/detail', 1]);
// 或者直接指定 path
_router.navigateByUrl('detail/1');
例子1:
Angular 应用从一个页面跳转到另一个新的页面,实质上是从一个配置项跳转到另一个配置项。在这个过程中, Angular 除会为配置项所对应的组件创建实例外,还会为该配置项本身创建一个 ActivatedRoute 实例来表示该配置项已被激活。该 ActivatedRoute 实例包含了一个快照(即 snapshot
属性),记录了从当前 URL 中解析出来的所有 Path 参数。下面展示了通讯录例子中的 DetailComponent 组件是如何通过快照来获取 Path 参数 的。示例代码如下:
// detail.component.ts
// 1. 导入 ActivatedRoute 服务
import { ActivatedRoute } from '@angular/router';
// ...
export class DetailComponent implements OnInit, OnDestroy {
contact_id: string;
constructor( private _activatedRoute:ActivatedRoute ) {
console.log('创建 DetailComponent 组件实例');
}
ngOnInit() {
// 2. 通过快照获取 Path 参数
this.contact_id = this._activatedRoute.snapshot.params['id'];
console.log('参数id的值为: '+this.contact_id);
}
//...
}
创建 DetailComponent 组件实例
参数id的值为: 1
例子2:
接下来介绍如何在通讯录例子页面跳转时获取参数值。首先在联系人详情组件的模板 detail.component.html 上添加一个链接,希望达到的效果是当单击该链接后,能够显示下一名联系人的信息。
<!-- detail.component.html -->
<div class="detail-contain">
<div class="header-detail">
<a [routerLink]="['']" class="back-to-list">
<i class="icon-back"></i>
所有联系人
</a>
<a [routerLink]="['/detail', nextContactId()]" class="back-to-list">
下一联系人
</a>
<!-- ... -->
</div>
<!-- ... -->
</div>
nextContactId()
方法通过简单地加 1 来获取下一名联系人的 id :
// detail.component.ts
// ...
export class DetailComponent implements OnInit, OnDestroy {
//...
nextContactId() {
return parseInt(this.contact_id) + 1;
}
}
例子3:
假设当前 URL 为 http://localhost:3000/detail/1
,在单击“下一联系人”链接后, URL 按照预期变成了 http://localhost:3000/detail/2
,但是页面上显示的仍然是原先联系人的信息。这是因为 Angular 在处理同一页面内跳转时,不会重新创建组件的实例,所以组件的构造函数和 ngOnInit() 方法都没有被调用到。虽然 Angular 会将快照中参数 id 的值更新为 2,但没有将这个更新通知到组件。为了解决这个问题, ActivatedRoute 服务提供了一个 Observable 对象,允许对参数的更新进行订阅。示例代码如下:
// detail.component.ts
// ...
export class DetailComponent implements OnInit, OnDestroy {
contact_id: string;
private sub: any;
//...
ngOnInit() {
this.sub = this._activatedRoute.params.subscribe(params => {
this.contact_id = params['id'];
console.log('参数id的值为: '+this.contact_id);
this.getById(this.contact_id);
});
}
ngOnDestroy() {
// 为了避免内存泄漏,在组件销毁时应该取消订阅
this.sub.unsubscribe();
}
//...
}
此时单击链接,便可以显示出下一位联系人的信息
Query 参数
我们也可以通过解析 URL 的 query 部分来获取参数值。由于 URL 的 query 部分不用于和配置项进行匹配,因此每一个配置项都可以拥有任意多个查询参数。下面的 URL 给联系人列表页定义了一个查询参数,表示只希望在页面上显示 5 位联系人。
http://localhost:3000/list?limit=5
Query 参数同样可以通过 RouterLink
指令或者跳转方法来赋值
<a [routerLink]="['/list']" [queryParams]="{limit: 5}">
this._router.navigate(['/list'], {queryParams: {limit: 5}});
this._router.navigateByUrl('/list?limit=5');
Query 参数的获取,需要借助于 ActivatedRoute 服务提供的 Observable 类型对象 queryParams
来完成。
例子4:
下面的代码通过获取 Query 参数来对显示在页面上的联系人数目进行限制
// list.component.ts
import { ActivatedRoute } from '@angular/router';
//...
export class ListComponentimplements OnInit, OnDestroy {
contacts: any[];
private limit: number;
private sub: any;
constructor(private _activatedRoute: ActivatedRoute) { }
ngOnInit() {
this.getContacts();
}
ngOnDestroy() {
this.sub.unsubscribe();
}
getContacts() {
//...
this.sub = this._activatedRoute.queryParams.subscribe(params => {
this.limit = parseInt(params['limit']);
if (this.limit) {
this.contacts.splice(this.limit);
}
});
}
//...
}
Matrix 参数(一般不用)
页面上所有组件都可以访问 Query 参数的内容,如果想精准地向某一个组件传递参数,则需要使用 Matrix 参数。
虽然 Matrix 写法未曾进入过 HTML 标准,但它是合法的。而且在浏览器的路由系统中,它作为从父路由和子路由中单独隔离出参数的方式而广受欢迎。Angular 的路由器正是这样一个路由系统,并支持跨浏览器的 Matrix 写法。
<a [routerLink]="['/detail', this.contact_id, {after:'2015-01-01', before
:'2015-12-31'}, 'album', {after:'2016-01-01', before:'2016-12-31'}]">Link</a>
Angular 会将该对象的属性转化为以“ ;”为分隔符的键值对,拼接到与该对象左边最近的 URL 分段上。依据上述链接参数数组生成的 URL 如下, DetailComponent 组件和 AlbumComponent 组件都将获得不同的参数值:
http://localhost:3000/detail/6;after=2015-01-01;before=2015-12-31/album;after=2016-01-01;before=2016-12-311
这种在一个 URL 分段内使用“ ;”分隔键值对的方式称为 Matrix URI,由互联网之父 Tim Berners-Lee 于 1996 年提出。根据其定义,每一个 URL 分段都可以拥有任意多个键值对,每个键值对只为其所在的分段服务。虽然 Matrix URI 一直没有进入 HTML 标准,但它能够清晰地表示出每一个 URL 分段所具有的键值对。 Angular 利用这个特性,将 Matrix 参数精准地传递给分段所对应的组件。 Matrix 参数的获取方式和 Path 参数一样,可以通过 ActivatedRoute 服务提供的快照和 Observable 对象两种方式来获取,在此不再赘述
子路由和附属 Outlet
嵌套路由
随着你的应用变得越来越复杂,你可能要创建一些根组件之外的相对路由。这些嵌套路由类型称为子路由。这意味着你要为你的应用添加第二 <[router-outlet](https://angular.cn/api/router/RouterOutlet)>
,因为它是 AppComponent
之外的另一个 <[router-outlet](https://angular.cn/api/router/RouterOutlet)>
。
在这个例子中,还有两个子组件,child-a
和 child-b
。这里的 FirstComponent
有它自己的 <nav>
和 AppComponent
之外的第二 <[router-outlet](https://angular.cn/api/router/RouterOutlet)>
。
<h2>First Component</h2>
<nav>
<ul>
<li><a routerLink="child-a">Child A</a></li>
<li><a routerLink="child-b">Child B</a></li>
</ul>
</nav>
<router-outlet></router-outlet>
子路由和其它路由一样,同时需要 path
和 component
。唯一的区别是你要把子路由放在父路由的 children
数组中。
const routes: Routes = [
{
path: 'first-component',
component: FirstComponent, // this is the component with the <router-outlet> in the template
children: [
{
path: 'child-a', // child route path
component: ChildAComponent, // child route component that the router renders
},
{
path: 'child-b',
component: ChildBComponent, // another child route component that the router renders
},
],
},
];
用命名出口(outlet)显示多重路由
Angular 允许一个路由组件包含多个 Outlet,从而可以在一个路由组件中同时显示多个组件。其中,主要 Outlet(Primary Outlet)有且仅有一个,附属 Outlet(Auxiliary Outlet)可以有任意多个,各个附属 Outlet 通过不同的命名加以区分。每一个 Outlet 均可以通过路由配置来指定其可以显示的组件,这使得 Angular 可以灵活地对各个组件进行组合,从而满足不同场景的需求。在通讯录例子中,假设想灵活地在 DetailComponent 组件中控制 AnnotationComponent 组件和相册 AlbumComponent 组件的显示,那么首先可以在 DetailComponent 组件的模板中添加一个名为 aux 的附属 Outlet。
<!--detail.component.html -->
<div class="detail-contain">
<!-- ... -->
<router-outlet></router-outlet>
<router-outlet name="aux"></router-outlet>
</div>
接下来在路由配置中定义在主要 Outlet 和附属 Outlet 上均可以显示 AnnotationComponent 组件和相册 AlbumComponent 组件。
export const rootRouterConfig: Routes = [
{
path: 'detail/:id', component: DetailComponent,
children: [
// 主要 Outlet
{ path: '', component: AnnotationComponent },
{ path: 'album', component: AlbumComponent },
// 附属 Outlet
{ path: 'annotation', component: AnnotationComponent, outlet: 'aux' },
{ path: 'album', component: AlbumComponent, outlet: 'aux' },
]
}
];
以 id 为 1 的联系人为例,下表列出了各种可能的组合及其对应的 URL 和链接参数数组。在链接参数数组中,如果一个元素包含了 outlets 属性,则表示该元素将用于为各个 Outlet 进行配置项匹配
链接参数数组
链接参数数组保存路由导航时所需的成分:
-
指向目标组件的那个路由的路径(path)
-
必备路由参数和可选路由参数,它们将进入该路由的 URL
可以把 RouterLink
指令绑定到一个数组,就像这样:
<a [routerLink]="['/heroes']">Heroes</a>
在指定路由参数时,使用如下的两元素数组:
<a [routerLink]="['/hero', hero.id]">
<span class="badge">{{ hero.id }}</span>{{ hero.name }}
</a>
可以在对象中提供可选的路由参数,比如 { foo: 'foo' }
:
<a [routerLink]="['/crisis-center', { foo: 'foo' }]">Crisis Center</a>
这三个例子涵盖了你在单级路由的应用中所需的一切。不过,在你添加一个像危机中心一样的子路由时,你可以创建新链接数组。
下面这个最小化 RouterLink
例子是基于危机中心指定的默认子路由构建的。
<a [routerLink]="['/crisis-center']">Crisis Center</a>
查看以下内容:
-
数组中的第一个条目标记出了父路由(
/crisis-center
)。 -
这个父路由没有参数。
-
没有默认的子路由,因此你得选取一个。
-
你决定跳转到
CrisisListComponent
,它的路由路径是'/',但你不用显式的添加它。
考虑以下路由器链接,它将从应用的根目录导航到巨龙危机(Dragon Crisis):
<a [routerLink]="['/crisis-center', 1]">Dragon Crisis</a>
-
数组中的第一个条目标记出了父路由(
/crisis-center
)。 -
这个父路由没有参数。
-
数组中的第二个条目('/:id')用来标记出到指定危机的详情页的子路由。
-
详细的子路由需要一个
id
路由参数。 -
你把巨龙危机的
id
添加为该数组中的第二个条目(1
)。 -
最终生成的路径是
/crisis-center/1
。
你也可以把危机中心的路由单独重新定义为 AppComponent
的模板:
template: `
<h1 class="title">Angular Router</h1>
<nav>
<a [routerLink]="['/crisis-center']">Crisis Center</a>
<a [routerLink]="['/crisis-center/1', { foo: 'foo' }]">Dragon Crisis</a>
<a [routerLink]="['/crisis-center/2']">Shark Crisis</a>
</nav>
<router-outlet></router-outlet>
`
总之,你可以用一级、两级或多级路由来写应用程序。链接参数数组提供了用来表示任意深度路由的链接参数数组以及任意合法的路由参数序列、必须的路由器参数以及可选的路由参数对象。
防止未经授权的访问
使用路由守卫来防止用户未经授权就导航到应用的某些部分。Angular 中提供了以下路由守卫:
-
canActivate
激活拦截。 -
canActivateChild
与 CanActivate 类似,用于控制是否允许激活子路由配置项。 -
canDeactivate
反激活拦截。 -
resolve
数据预加载拦截。 -
canLoad
模块加载拦截。
要想使用路由守卫,可以考虑使用无组件路由,因为这对于保护子路由很方便。
为你的守卫创建一项服务:
请在守卫函数里实现你要用到的守卫。下面的例子使用 canActivate
来保护该路由。
export const yourGuard: CanActivateFn = (
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot) => {
// your logic goes here
}
在路由模块中,在 routes
配置中使用相应的属性。这里的 canActivate
会告诉路由器它要协调到这个特定路由的导航。
{
path: '/your-path',
component: YourComponent,
canActivate: [yourGuard],
}
关于此可工作范例的更多信息,请参阅路由导航中关于路由守卫的部分。
惰性加载
https://angular.cn/guide/lazy-loading-ngmodules
标签:URL,component,参数,组件,path,angular,路由 From: https://www.cnblogs.com/tangge/p/17121141.html