发布于 2026-01-06 3 阅读
0

在 Angular 中创建简单的面包屑导航 配置路由面包屑导航组件 结论

在 Angular 中创建一个简单的面包屑导航

配置路由

面包屑组件




结论

注意:本文撰写于 2018 年,现已存档。此解决方案可能不适用于最新版本的 Angular。如果您想了解其背后的思路,可以继续阅读,但可能不需要按照文中的实现方式操作,因为它已经过时。谢谢!

访问我的博客查看原文:在 Angular 中创建一个简单的面包屑导航

最近,我正在为公司搭建一个企业资源计划(ERP)平台。该系统需要具备灵活性,能够容纳不同的独立模块。在这个平台上,用户导航应该清晰简洁,以便用户在执行任务时能够方便地了解自己所处的位置。

例如,可以提供类似“仪表盘 -> IT 服务台 -> 问题日志 -> 新建”这样的层级结构作为位置参考。更重要的是,用户可以方便地返回到不同的页面层级。因此,我开发了一个面包屑导航组件来满足这一需求。

静态链接示例:
Imgur

动态链接示例(123 为动态 ID):
Imgur

配置路由

首先,你需要正确配置你的路由。

“仪表盘”->“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
                        }
                    ]
                }
            ]
        }
    ]
}


Enter fullscreen mode Exit fullscreen mode

为了使用面包屑导航,我们需要从路由配置中获取路径名称,因为面包屑导航中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: ''
                            }
                        }
                    ]
                },
            ]
        }
    ]
}


Enter fullscreen mode Exit fullscreen mode

请注意,该路由issue-log/:id目前还没有面包屑导航数据。这是因为该路由包含动态参数。我们将在稍后构建面包屑导航时自动显示文本。

面包屑组件

HTML

HTML 部分相当简单。只需使用 `<br>`olli`<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>


Enter fullscreen mode Exit fullscreen mode

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: "";
  }
}


Enter fullscreen mode Exit fullscreen mode

TypeScript

最重要的部分是 TypeScript 部分。

界面

首先要做的是创建一个接口,以规范面包屑导航的数据结构。

breadcrumb.interface.ts



export interface IBreadCrumb {
  label: string;
  url: string;
}


Enter fullscreen mode Exit fullscreen mode

成分

接下来我们就可以开始构建面包屑导航组件了。基本代码结构如下所示。



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
  }
}


Enter fullscreen mode Exit fullscreen mode

如您所见,我们需要实现两个功能。

ngOnInit()该函数在组件创建时立即触发。在这个函数中,我们将获取当前路由并从其根路由开始构建面包屑导航。

buildBreadCrumb()这是我们实际构建面包屑导航的函数。它是一个递归函数,用于递归地遍历路由对象的子节点,从根节点到叶节点,例如从“仪表盘”一直到“问题日志”

构建面包屑()

  1. 首先,我们来获取单个面包屑导航的标签和路径。注意,当前路径可能位于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 : "";


Enter fullscreen mode Exit fullscreen mode
  1. 其次,我们需要处理动态参数,例如::id请看这条路由。


{
    path: 'issue-log/:id',
    component: IssueLogDetailComponent
    data: {
        breadcrumb: ''
    }
}


Enter fullscreen mode Exit fullscreen mode

面包屑导航之前是空白的,因为路由是动态的。我只能在运行时才能知道路由 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];
}


Enter fullscreen mode Exit fullscreen mode
  1. 生成下一个 URL

在路由的每次递归循环中,路径都是片段化的,无法获得完整的路径,例如,路径为 `//1` 而不是 ` issue-log//2` dashboard/it-helpdesk/issue-log。因此,需要重新构建完整的路径并将其附加到当前级别的面包屑导航中。



const nextUrl = path ? `${url}/${path}` : url;

const breadcrumb: IBreadCrumb = {
  label: label,
  url: nextUrl
};


Enter fullscreen mode Exit fullscreen mode
  1. 添加非空标签和递归调用的路由

在您的应用程序中,可能存在一些未设置面包屑导航的路由,构建器应忽略这些路由。

接下来,如果当前路由有子路由,则表示该路由还不是叶子路由,我们需要继续递归调用来构建下一级路由。



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;


Enter fullscreen mode Exit fullscreen mode
  1. 完整图片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;
}


Enter fullscreen mode Exit fullscreen mode

ngOnInit()

最后,我们需要实现ngOnInit()触发功能来开始构建面包屑导航。

当检测到路由变更事件时,面包屑导航应该开始构建。为了检测到这种变更,我们使用RxJS来观察这些变更。



ngOnInit() {
    this.router.events.pipe(
        filter((event: Event) => event instanceof NavigationEnd),
        distinctUntilChanged(),
    ).subscribe(() => {
        this.breadcrumbs = this.buildBreadCrumb(this.activatedRoute.root);
    })
}


Enter fullscreen mode Exit fullscreen mode

上面的代码片段表明,通过筛选事件类型为 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"
}
];

Enter fullscreen mode Exit fullscreen mode




结论

面包屑导航实现的算法相当简单,但我认为它令人困惑的地方在于配置。作为开发者,你需要知道在哪里进行配置,以及Angular提供的功能。如果对Angular有深入的了解,你可以轻松地实现一些组件,因为Angular已经提供了大部分所需的工具。

您可以在这里查看完整代码:GitHub

感谢阅读~

文章来源:https://dev.to/zhiyueyi/create-a-simple-breadcrumb-in-angular-ag5