angular - 路由
路由配置
最简单,一个配置项包含了 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})],
// ...
})
设置通配符路由
当用户试图导航到那些不存在的应用部件时,在正常的应用中应该能得到很好的处理。要在应用中添加此功能,需要设置通配符路由。当所请求的 URL 与任何路由器路径都不匹配时,Angular 路由器就会选择这个路由。
要设置通配符路由,请在 routes
定义中添加以下代码。
{ path: '**', component: <component-name> }
这两个星号 **
告诉 Angular,这个 routes
定义是通配符路由。对于 component 属性,你可以使用应用中的任何组件。常见的选择包括应用专属的 PageNotFoundComponent
,你可以定义它来向用户展示 404 页面,或者跳转到应用的主组件。通配符路由是最后一个路由,因为它匹配所有的 URL。关于路由顺序的更多详细信息,请参阅路由顺序。
显示 404 页面
要显示 404 页面,请设置一个通配符路由,并将 component
属性设置为你要用于 404 页面的组件,如下所示:
const routes: Routes = [
{ path: 'first-component', component: FirstComponent },
{ path: 'second-component', component: SecondComponent },
{ path: '**', component: PageNotFoundComponent }, // Wildcard route for a 404 page
];
path
为 **
的最后一条路由是通配符路由。如果请求的 URL 与前面列出的路径不匹配,路由器会选择这个路由,并把该用户送到 PageNotFoundComponent
。
设置重定向
要设置重定向,请使用重定向源的 path
、要重定向目标的 component
和一个 pathMatch
值来配置路由,以告诉路由器该如何匹配 URL。
AppRoutingModule
const routes: Routes = [
{ path: 'first-component', component: FirstComponent },
{ path: 'second-component', component: SecondComponent },
{ path: '', redirectTo: '/first-component', pathMatch: 'full' }, // redirect to `first-component`
{ path: '**', component: PageNotFoundComponent }, // Wildcard route for a 404 page
];
在这个例子中,第三个路由是重定向路由,所以路由器会默认跳到 first-component
路由。注意,这个重定向路由位于通配符路由之前。这里的 path: ''
表示使用初始的相对 URL(''
)。
关于 pathMatch
的详情,请参阅聚焦 pathMatch
部分。
路由跳转
链接参数数组
链接参数数组保存路由导航时所需的成分:
-
指向目标组件的那个路由的路径(path)
-
必备路由参数和可选路由参数,它们将进入该路由的 URL
可以把 RouterLink
指令绑定到一个数组,就像这样:
<a [routerLink]="['/heroes']">Heroes</a>
在指定路由参数时,使用如下的两元素数组:
<a [routerLink]="['/hero', hero.id]">
<span class="badge">{{ hero.id }}</span>{{ hero.name }}
</a>
<!--if hero.id = 1-->
http://localhost:4200/hero/1
可以在对象中提供可选的路由参数,比如 { foo: 'foo' }
:
<a [routerLink]="['/crisis-center', { foo: 'foo' }]">Crisis Center</a>
http://localhost:4200/crisis-center;foo=foo
这三个例子涵盖了你在单级路由的应用中所需的一切。不过,在你添加一个像危机中心一样的子路由时,你可以创建新链接数组。
下面这个最小化 RouterLink
例子是基于危机中心指定的默认子路由构建的。
<a [routerLink]="['/crisis-center']">Crisis Center</a>
查看以下内容:
-
数组中的第一个条目标记出了父路由(
/crisis-center
)。 -
这个父路由没有参数。
-
没有默认的子路由,因此你得选取一个。
-
你决定跳转到
CrisisListComponent
,它的路由路径是'/',但你不用显式的添加它。
考虑以下路由器链接,它将从应用的根目录导航到巨龙危机(Dragon Crisis):
<a [routerLink]="['/crisis-center', 1]">Dragon Crisis</a>
http://localhost:4200/hero/1
-
数组中的第一个条目标记出了父路由(
/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>
`
总之,你可以用一级、两级或多级路由来写应用程序。链接参数数组提供了用来表示任意深度路由的链接参数数组以及任意合法的路由参数序列、必须的路由器参数以及可选的路由参数对象。
使用指令跳转
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 对象两种方式来获取,在此不再赘述
ActivatedRoute 的4个参数的区别: param, queryParams, paramMap, queryParamMap
ActivatedRoute
是 Angular 的一个服务,它提供了对路由信息的访问。每个属性都有其特定的用途。
params
: 这个属性是一个Params
,包含了当前路由的路径参数。路径参数是通过在路由路径中使用冒号(:)来定义的,例如在/user/:id
中,id
就是一个路径参数。你可以通过这个属性来获取这个参数的值。queryParams
: 这个属性也是一个Params
,包含了当前 URL 的查询参数。查询参数是在 URL 的问号(?)之后的部分,例如在/path?param1=value1¶m2=value2
中,param1
和param2
就是查询参数。paramMap
: 这个属性也是一个ParamMap
,与params
类似,但是它提供了更灵活的方法来处理和解析参数。你可以使用paramMap.get(name)
来获取特定的参数。queryParamMap
: 这个属性是一个ParamMap
,与queryParams
类似,但是它也提供了更灵活的方法来处理和解析参数。你可以使用queryParamMap.get(name)
来获取特定的查询参数。
什么时候使用这些属性取决于你的需求。如果你只需要访问路径参数,你可以使用 params
和 paramMap
。如果你需要访问查询参数,你可以使用 queryParams
和 queryParamMap
。这些属性在导航到不同的路由或者在处理用户输入时特别有用。
子路由和附属 Outlet
嵌套路由
随着你的应用变得越来越复杂,你可能要创建一些根组件之外的相对路由。这些嵌套路由类型称为子路由。这意味着你要为你的应用添加第二 <router-outlet>
,因为它是 AppComponent
之外的另一个 router-outlet
。
在这个例子中,还有两个子组件,child-a
和 child-b
。这里的 FirstComponent
有它自己的 <nav>
和 AppComponent
之外的第二 router-outlet
。
<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' },
]
}
];
<a [routerLink]='[{ outlets: { popup: ['aux'] } }]'></a>
以 id 为 1 的联系人为例,下表列出了各种可能的组合及其对应的 URL 和链接参数数组。在链接参数数组中,如果一个元素包含了 outlets 属性,则表示该元素将用于为各个 Outlet 进行配置项匹配
惰性加载
https://angular.cn/guide/lazy-loading-ngmodules
惰性加载可以减小初始包的尺寸,从而减少加载时间。
要惰性加载 Angular 模块,请在 AppRoutingModule
routes
中使用 loadChildren
代替 component
进行配置,
const routes: Routes = [
{
path: 'items',
loadChildren: () => import('./items/items.module').then(m => m.ItemsModule)
}
];
接下来,你将需要一个包含路由的目标组件的特性模块。要创建它,在命令行工具中输入如下命令,其中 children-module
是特性模块的名称。加载 children-module
特性模块的路径也是 children-module
,因为它是通过 --route
选项指定的:
ng generate module children-module --route children-module --module app.module
这将创建一个 children-module
目录,在其 children-module.module.ts
文件中定义了新的可惰性加载模块 ChildrenModuleModule
。该命令会自动在新特性模块中声明 ChildrenModuleModuleComponent
。
因为我们直接引用 ListComponent
,删除掉相关的 children-module.component
就OK了。
forRoot() 与 forChild()
你可能已经注意到了,Angular CLI 会把 RouterModule.forRoot(routes)
添加到 AppRoutingModule
的 imports
数组中。这会让 Angular 知道 AppRoutingModule
是一个路由模块,而 forRoot()
表示这是一个根路由模块。它会配置你传入的所有路由、让你能访问路由器指令并注册 Router。forRoot()
](https://angular.cn/api/router/Router)%E3%80%82%60forRoot()%60) 在应用中只应该使用一次,也就是这个 AppRoutingModule
中。
Angular CLI 还会把 RouterModule.forChild(routes)
添加到各个特性模块中。这种方式下 Angular 就会知道这个路由列表只负责提供额外的路由并且其设计意图是作为特性模块使用。你可以在多个模块中使用 forChild()
。
forRoot()
方法为路由器管理全局性的注入器配置。forChild()
方法中没有注入器配置,只有像 RouterOutlet 和 RouterLink 这样的指令。欲知详情,参阅单例服务章的 forRoot()
模式小节。
预加载
https://angular.cn/guide/lazy-loading-ngmodules#preloading
防止未经授权的访问
使用路由守卫来防止用户未经授权就导航到应用的某些部分。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],
}
关于此可工作范例的更多信息,请参阅路由导航中关于路由守卫的部分。
命令
>ng g guard guards/authGuard //命令
? Which type of guard would you like to create? CanActivate
CREATE src/app/guards/auth-guard.guard.ts (462 bytes)
canActivate
类可以实现的接口,作为决定是否可以激活路由的守卫。如果返回 true
,则导航继续。如果返回 false
,则导航被取消。如果返回 UrlTree
,则当前导航被取消,并开始对返回的 UrlTree
进行新的导航。
import { Injectable, inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthGuardGuard implements CanActivate {
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
console.log(route)
return false;
}
}
UrlTree 跳转
CanActivateFn
CanActivate 被废弃了,基于类的“Route”保护已被弃用,取而代之的是功能性保护。一
*可注入类可以用作使用“inject”函数的功能保护:
import { Injectable, inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, CanActivateFn, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
@Injectable()
export class AuthGuardGuard {
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
console.log(route)
return false;
}
}
export const authGuardGuard: CanActivateFn =
(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
return inject(AuthGuardGuard).canActivate(route, state);
};
//https://angular.cn/api/router/CanActivateFn#description
CanActivateChild
CanActivate
用来限制从特定路径的访问和所有的子路径和CanActivateChild
被用来限制CanActivate
路径内部访问一个特定的组。
例如,你可能有一个管理模块,所有路由都需要防止未经授权的进入加以防护:
{
path: 'admin',
component: AdminComponent,
canActivate: [ AuthGuardService ],
children : [
{
path: '', component: ...,
},
{
path: 'manage-users', component: ...,
},
{
path: 'manage-roles', component: ...,
}
]
}
这将需要保护的从上而下。没有未经授权的访问任何路线,包括根和儿童。在这种情况下,canActivate
在根本上很好地保护了一切。
但你也可能有,你有一个功能模块,其中只有某些孩子需要加以防护时间:
{
path: 'featureA',
component: ...,
canActivateChild: [ AuthGuardService ],
children : [
{
path: 'manage-feature', component: ...,
},
{
path: 'manage-members', component: ...,
}
],
{path: 'featureB', component: ...}
}
在这种情况下,也许所有用户都需要正本清源组件的featureA“和'featureB',但只有某些用户需要能够导航到'featureA'的子路由。在这种情况下,在根级别使用一名警卫人员更容易保护儿童,但不是根本身。另一种方法是在每条儿童路线上放置canActivate
警卫,这可能会很乏味。
这一切都取决于您的要求,但它可以很好同时具有canActivate
和canActivateChild
的选项。
Resolve
数据预加载拦截
创建命令 ng g resolver <name>