在 Angular 中创建一个简单的面包屑导航
配置路由
面包屑组件
结论
注意:本文撰写于 2018 年,现已存档。此解决方案可能不适用于最新版本的 Angular。如果您想了解其背后的思路,可以继续阅读,但可能不需要按照文中的实现方式操作,因为它已经过时。谢谢!
访问我的博客查看原文:在 Angular 中创建一个简单的面包屑导航
最近,我正在为公司搭建一个企业资源计划(ERP)平台。该系统需要具备灵活性,能够容纳不同的独立模块。在这个平台上,用户导航应该清晰简洁,以便用户在执行任务时能够方便地了解自己所处的位置。
例如,可以提供类似“仪表盘 -> IT 服务台 -> 问题日志 -> 新建”这样的层级结构作为位置参考。更重要的是,用户可以方便地返回到不同的页面层级。因此,我开发了一个面包屑导航组件来满足这一需求。
配置路由
首先,你需要正确配置你的路由。
以“仪表盘”->“IT服务台”->“问题日志”->“新建”为例。以下代码片段展示了一个基本的路由结构。
{
path: '',
component: LoginComponent,
}, {
path: 'dashboard',
component: DashboardComponent,
children: [
{
path: 'it-helpdesk',
component: ItHelpdeskComponent,
children: [
{
path: 'issue-log',
children: [
{
path: '',
component: IssueLogListComponent
},
{
path: 'new',
component: IssueLogDetailComponent
},
{
path: ':id',
component: IssueLogDetailComponent
}
]
}
]
}
]
}
为了使用面包屑导航,我们需要从路由配置中获取路径名称,因为面包屑导航中issue-log会显示相应的路由名称。然后我们使用属性来存储路径的显示名称。因此,我们需要修改路由配置如下。Issue LogdataRoute
{
path: '',
component: LoginComponent,
}, {
path: 'dashboard',
component: DashboardComponent,
data: {
breadcrumb: 'Dashboard',
},
children: [
{
path: 'it-helpdesk',
component: ItHelpdeskComponent,
data: {
breadcrumb: 'IT Helpdesk'
},
children: [
{
path: 'issue-log',
data: {
breadcrumb: 'Issue Log'
},
children: [
{
path: '',
component: IssueLogListComponent
},
{
path: 'new',
component: IssueLogDetailComponent,
data: {
breadcrumb: 'New'
}
},
{
path: ':id',
component: IssueLogDetailComponent,
data: {
breadcrumb: ''
}
}
]
},
]
}
]
}
请注意,该路由issue-log/:id目前还没有面包屑导航数据。这是因为该路由包含动态参数。我们将在稍后构建面包屑导航时自动显示文本。
面包屑组件
HTML
HTML 部分相当简单。只需使用 `<br>`ol和li`<br>` 来列出所有面包屑导航即可。*ngFor
breadcrumb.component.html
<ol class="breadcrumb">
<li *ngFor="let breadcrumb of breadcrumbs">
<span [routerLink]="breadcrumb.url" routerLinkActive="router-link-active">
{{ breadcrumb.label }}
</span>
</li>
</ol>
SCSS
CSS 代码也不复杂。请注意,当鼠标悬停在面包屑导航上时,它应该变暗。
breadcrumb.component.scss
.breadcrumb {
background: none;
font-size: 0.8em;
margin: 0;
a,
span {
color: darkgrey;
}
a:hover,
span:hover {
color: dimgrey;
text-decoration: none;
}
li {
list-style: none;
float: left;
margin: 5px;
}
li:last-child {
margin-right: 20px;
}
li::after {
content: "->";
color: darkgrey;
}
li:last-child::after {
content: "";
}
}
TypeScript
最重要的部分是 TypeScript 部分。
界面
首先要做的是创建一个接口,以规范面包屑导航的数据结构。
breadcrumb.interface.ts
export interface IBreadCrumb {
label: string;
url: string;
}
成分
接下来我们就可以开始构建面包屑导航组件了。基本代码结构如下所示。
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, NavigationEnd } from '@angular/router';
import { IBreadCrumb } from '../../../interfaces/breadcrumb.interface';
import { filter, distinctUntilChanged } from 'rxjs/operators';
@Component({
selector: 'app-breadcrumb',
templateUrl: './breadcrumb.component.html',
styleUrls: ['./breadcrumb.component.scss']
})
export class BreadcrumbComponent implements OnInit {
public breadcrumbs: IBreadCrumb[]
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
) {
this.breadcrumbs = this.buildBreadCrumb(this.activatedRoute.root);
}
ngOnInit() {
// ... implementation of ngOnInit
}
/**
* Recursively build breadcrumb according to activated route.
* @param route
* @param url
* @param breadcrumbs
*/
buildBreadCrumb(route: ActivatedRoute, url: string = '', breadcrumbs: IBreadCrumb[] = []): IBreadCrumb[] {
// ... implementation of buildBreadCrumb
}
}
如您所见,我们需要实现两个功能。
ngOnInit()该函数在组件创建时立即触发。在这个函数中,我们将获取当前路由并从其根路由开始构建面包屑导航。
buildBreadCrumb()这是我们实际构建面包屑导航的函数。它是一个递归函数,用于递归地遍历路由对象的子节点,从根节点到叶节点,例如从“仪表盘”一直到“问题日志”。
构建面包屑()
- 首先,我们来获取单个面包屑导航的标签和路径。注意,当前路径可能位于根
routeConfig目录。因此,在赋值给变量之前必须先检查根目录,否则会抛出异常。nullrouteroute.routeConfig.data.breadcrumbroute.routeConfig.path
let label =
route.routeConfig && route.routeConfig.data
? route.routeConfig.data.breadcrumb
: "";
let path =
route.routeConfig && route.routeConfig.data ? route.routeConfig.path : "";
- 其次,我们需要处理动态参数,例如:
:id请看这条路由。
{
path: 'issue-log/:id',
component: IssueLogDetailComponent
data: {
breadcrumb: ''
}
}
面包屑导航之前是空白的,因为路由是动态的。我只能在运行时才能知道路由 ID。
已激活的路由包含实际 ID。因此,我们将通过获取路由的最后一部分并检查其是否以 `<route_name>` 开头,动态地将实际 ID 附加到面包屑导航中。如果是,则表示这是一个动态路由,然后我们通过其参数名称 `<route_name>`:获取实际 ID 。route.snapshot.paramsparamName
const lastRoutePart = path.split("/").pop();
const isDynamicRoute = lastRoutePart.startsWith(":");
if (isDynamicRoute && !!route.snapshot) {
const paramName = lastRoutePart.split(":")[1];
path = path.replace(lastRoutePart, route.snapshot.params[paramName]);
label = route.snapshot.params[paramName];
}
- 生成下一个 URL
在路由的每次递归循环中,路径都是片段化的,无法获得完整的路径,例如,路径为 `//1` 而不是 ` issue-log//2` dashboard/it-helpdesk/issue-log。因此,需要重新构建完整的路径并将其附加到当前级别的面包屑导航中。
const nextUrl = path ? `${url}/${path}` : url;
const breadcrumb: IBreadCrumb = {
label: label,
url: nextUrl
};
- 添加非空标签和递归调用的路由
在您的应用程序中,可能存在一些未设置面包屑导航的路由,构建器应忽略这些路由。
接下来,如果当前路由有子路由,则表示该路由还不是叶子路由,我们需要继续递归调用来构建下一级路由。
const newBreadcrumbs = breadcrumb.label
? [...breadcrumbs, breadcrumb]
: [...breadcrumbs];
if (route.firstChild) {
//If we are not on our current path yet,
//there will be more children to look after, to build our breadcumb
return this.buildBreadCrumb(route.firstChild, nextUrl, newBreadcrumbs);
}
return newBreadcrumbs;
- 完整图片
buildBreadCrumb()
/**
* Recursively build breadcrumb according to activated route.
* @param route
* @param url
* @param breadcrumbs
*/
buildBreadCrumb(route: ActivatedRoute, url: string = '', breadcrumbs: IBreadCrumb[] = []): IBreadCrumb[] {
//If no routeConfig is avalailable we are on the root path
let label = route.routeConfig && route.routeConfig.data ? route.routeConfig.data.breadcrumb : '';
let path = route.routeConfig && route.routeConfig.data ? route.routeConfig.path : '';
// If the route is dynamic route such as ':id', remove it
const lastRoutePart = path.split('/').pop();
const isDynamicRoute = lastRoutePart.startsWith(':');
if(isDynamicRoute && !!route.snapshot) {
const paramName = lastRoutePart.split(':')[1];
path = path.replace(lastRoutePart, route.snapshot.params[paramName]);
label = route.snapshot.params[paramName];
}
//In the routeConfig the complete path is not available,
//so we rebuild it each time
const nextUrl = path ? `${url}/${path}` : url;
const breadcrumb: IBreadCrumb = {
label: label,
url: nextUrl,
};
// Only adding route with non-empty label
const newBreadcrumbs = breadcrumb.label ? [ ...breadcrumbs, breadcrumb ] : [ ...breadcrumbs];
if (route.firstChild) {
//If we are not on our current path yet,
//there will be more children to look after, to build our breadcumb
return this.buildBreadCrumb(route.firstChild, nextUrl, newBreadcrumbs);
}
return newBreadcrumbs;
}
ngOnInit()
最后,我们需要实现ngOnInit()触发功能来开始构建面包屑导航。
当检测到路由变更事件时,面包屑导航应该开始构建。为了检测到这种变更,我们使用RxJS来观察这些变更。
ngOnInit() {
this.router.events.pipe(
filter((event: Event) => event instanceof NavigationEnd),
distinctUntilChanged(),
).subscribe(() => {
this.breadcrumbs = this.buildBreadCrumb(this.activatedRoute.root);
})
}
上面的代码片段表明,通过筛选事件类型为 NavigationEnd 且具有明显变化的事件来观察路由器事件。
这意味着如果路线发生变化,且新值与先前值不同,则面包屑导航将开始构建。递归函数的结果将存储在this.breadcrumb一个数组中,如下所示。
[
{
label: "Dashboard",
url: "/dashboard"
},
{
label: "IT Helpdesk",
url: "/dashboard/it-helpdesk"
},
{
label: "Issue Log",
url: "/dashboard/it-helpdesk/issue-log"
},
{
label: "plfOR05NXxQ1",
url: "/dashboard/it-helpdesk/issue-log/plfOR05NXxQ1"
}
];
结论
面包屑导航实现的算法相当简单,但我认为它令人困惑的地方在于配置。作为开发者,你需要知道在哪里进行配置,以及Angular提供的功能。如果对Angular有深入的了解,你可以轻松地实现一些组件,因为Angular已经提供了大部分所需的工具。
您可以在这里查看完整代码:GitHub
感谢阅读~
文章来源:https://dev.to/zhiyueyi/create-a-simple-breadcrumb-in-angular-ag5

