داشتن Breadcrumbs یکی از گزینههای مفید بهبود کاربری هر سایتی است و در برنامههای Angular با کوئری گرفتن از سیستم مسیریابی آن میتوان به سادگی آنها را تولید کرد.
ایجاد ساختاری برای نگهداری آرایهی breadcrumbs
کامپوننت نمایش breadcrumbs را در مسیر
src\app\core\bread-crumb ایجاد میکنیم. یعنی قصد داریم آنرا به
CoreModule برنامه اضافه کنیم؛ از این جهت که کامپوننت آن، تکمیل کنندهی app.component است و هر کامپوننتی که تنها در این کامپوننت ویژه بکار رود، محل تعریف آن در CoreModule خواهد بود.
به همین جهت کامپوننت bread-crumb را به صورت ذیل ایجاد میکنیم:
که تعاریف آن در فایل src\app\core\core.module.ts در قسمت exports و declarations درج خواهد شد:
import { BreadCrumbComponent } from "./bread-crumb/bread-crumb.component";
@NgModule({
imports: [CommonModule, RouterModule],
exports: [
// components that are used in app.component.ts will be listed here.
BreadCrumbComponent
],
declarations: [
// components that are used in app.component.ts will be listed here.
BreadCrumbComponent
]
})
export class CoreModule {}
و سپس اینترفیس bread-crumb.ts را در مسیر src\app\core\bread-crumb\bread-crumb.ts به نحو زیر تعریف میکنیم:
export interface BreadCrumb {
label: string;
url: string;
};
برچسب و url، دو حداقل پیشفرض نمایش قطعات منوی breadcrumbs هستند.
تکمیل کامپوننت Breadcrumb
کدهای کامل کامپوننت Breadcrumb را در ذیل مشاهده میکنید:
import { Component, OnInit, ViewEncapsulation } from "@angular/core";
import { ActivatedRoute, NavigationEnd, Router } from "@angular/router";
import { BreadCrumb } from "./bread-crumb";
import { Observable } from "rxjs/Observable";
@Component({
selector: "app-bread-crumb",
templateUrl: "./bread-crumb.component.html",
styleUrls: ["./bread-crumb.component.css"],
encapsulation: ViewEncapsulation.None
})
export class BreadCrumbComponent implements OnInit {
breadcrumbs$: Observable<BreadCrumb[]>;
constructor(private activatedRoute: ActivatedRoute, private router: Router) { }
ngOnInit() {
this.breadcrumbs$ = this.router.events
.filter(event => event instanceof NavigationEnd)
.distinctUntilChanged()
.map(event => event ? this.buildBreadCrumbs(this.activatedRoute.root) : []);
}
buildBreadCrumbs(route: ActivatedRoute, url: string = "", breadcrumbs: Array<BreadCrumb> = []): Array<BreadCrumb> {
const routeDataBreadCrumbKey = "breadcrumb";
const routeConfig = route.routeConfig;
const path = routeConfig && routeConfig.path !== undefined ? routeConfig.path : "";
let label = path;
if (url === "") {
label = "Home";
} else if (routeConfig && routeConfig.data !== undefined) {
label = routeConfig.data[routeDataBreadCrumbKey];
}
const nextUrl = `${url}${path}/`;
const breadcrumb: BreadCrumb = {
label: label,
url: nextUrl
};
console.log("breadcrumb", { path: path, label: label, url: nextUrl, route: route });
const newBreadcrumbs = [...breadcrumbs, breadcrumb];
if (route.firstChild) {
return this.buildBreadCrumbs(route.firstChild, nextUrl, newBreadcrumbs);
}
return newBreadcrumbs;
}
}
توضیحات:
در اینجا در ابتدای کار، مشترک رخداد NavigationEnd سیستم مسیریابی خواهیم شد:
ngOnInit() {
this.breadcrumbs$ = this.router.events
.filter(event => event instanceof NavigationEnd)
.distinctUntilChanged()
.map(event => event ? this.buildBreadCrumbs(this.activatedRoute.root) : []);
}
هر زمانیکه رخداد مرور صفحهی جاری به پایان رسید، بر اساس مسیر ریشهی آن، متد buildBreadCrumbs فراخوانی میشود. این متد، یک متد بازگشتی است. از این جهت که مسیر جاری میتواند حاصل مرور یک مسیر والد و سپس چندین مسیر تو در توی فرزند و والد آن باشد. برای نمونه ممکن است مانند تصویر ابتدای بحث،
مسیریابی شما به صورت ذیل تعریف شده باشد:
const routes: Routes = [
{
path: "breadCrumbTest",
data: { breadcrumb: "Parent1" },
children: [
{
path: "", component: Parent1Component
},
{
path: "Parent1Child1",
data: { breadcrumb: "Parent1-Child1" },
children: [
{
path: "", component: Parent1Child1Component
},
{
path: "Parent1Child1Child1", component: Parent1Child1Child1Component,
data: { breadcrumb: "Parent1-Child1 Child1" }
}
]
}
]
}
];
در اینجا چندین مسیر والد و فرزند تو در تو را مشاهده میکنید.
بر اساس قراردادی که در کامپوننت نمایش bread-crumbs درنظر گرفتهایم، عنوان هر مسیر در خاصیت data آن با کلید breadcrumb درج میشود:
data: { breadcrumb: "Parent1-Child1 Child1" }
مقدار این خاصیت را به صورت ذیل در متد buildBreadCrumbs میخوانیم:
buildBreadCrumbs(route: ActivatedRoute, url: string = "", breadcrumbs: Array<BreadCrumb> = []): Array<BreadCrumb> {
const routeDataBreadCrumbKey = "breadcrumb";
const routeConfig = route.routeConfig;
const path = routeConfig && routeConfig.path !== undefined ? routeConfig.path : "";
let label = path;
if (url === "") {
label = "Home";
} else if (routeConfig && routeConfig.data !== undefined) {
label = routeConfig.data[routeDataBreadCrumbKey];
}
برای بار اول فراخوانی متد بازگشتی buildBreadCrumbs، مقدار url خالی است. به همین جهت برچسب home را به آن نسبت خواهیم داد. در بار بعدی، اطلاعات path و data مسیر فعال شده را از شیء route.routeConfig استخراج کرده و توسط آن یک bread-crumb جدید را ایجاد میکنیم:
const nextUrl = `${url}${path}/`;
const breadcrumb: BreadCrumb = {
label: label,
url: nextUrl
};
سپس اگر این مسیر فرزندی را داشته باشد، مقدار خاصیت route.firstChild آن نال نبوده و میتوان متد بازگشتی را در همینجا با فراخوانی متد جاری تشکیل داد:
const newBreadcrumbs = [...breadcrumbs, breadcrumb];
if (route.firstChild) {
return this.buildBreadCrumbs(route.firstChild, nextUrl, newBreadcrumbs);
}
return newBreadcrumbs;
علت استفادهی از روش [breadcrumb, breadcrumbs...] بجای استفاده از متد push آرایه، مربوط است به
encapsulation: ViewEncapsulation.None
در مورد ViewEncapsulation در مطلب «
بررسی استراتژیهای تشخیص تغییرات در برنامههای Angular» بیشتر بحث کردهایم و تنظیم آن به None، کارآیی برنامه را با کاهش کار سیستم ردیابی تغییرات Angular، بهبود میبخشد.
تکمیل قالب نمایش Breadcrumbs
پس از تکمیل BreadCrumbComponent، اکنون قالب آن به صورت ذیل تعریف میشود:
<ol class="breadcrumb">
<li *ngFor="let breadcrumb of breadcrumbs$ | async" class="breadcrumb-item">
<a [routerLink]="[breadcrumb.url]">{{ breadcrumb.label }}</a>
</li>
</ol>
در اینجا بر روی آرایهی تشکیل شدهی توسط متد buildBreadCrumbs، یک حلقه ایجاد شده و عناصر آن نمایش داده میشوند. چون خاصیت عمومی breadcrumbs به صورت یک Observable تعریف شدهاست، در اینجا استفاده از async pipe را نیز در این حلقه مشاهده میکنید:
breadcrumbs$: Observable<BreadCrumb[]>;
نمایش نهایی BreadCrumbs
در پایان کافی است به فایل app.component.html مراجعه کرده و selector کامپوننت نمایش bread crumbs را در آن درج کنیم:
<div class="container">
<app-bread-crumb></app-bread-crumb>
<router-outlet></router-outlet>
</div>
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.