Reputation: 1141
I've been trying to build a breadcrumb navigation system for angular.
The problem I'm having is my breadcrumb.service seems to be listing the Home page twice....
Code is on Stackblitz here - Stackblitz Editor
It's something about going through the addBreadcrumb() loop the first time.
(Also, I give credit to https://github.com/ocanzillon/angular-breadcrumb as I started off looking over his project)
My breadcrumb.service.ts :
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Data, NavigationEnd, Router } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import { filter } from 'rxjs/operators';
import { Breadcrumb } from '@core/models/breadcrumb';
@Injectable({
providedIn: 'root'
})
export class BreadcrumbService {
// Subject emitting the breadcrumb hierarchy
private readonly _breadcrumbs$ = new BehaviorSubject<Breadcrumb[]>([]);
// Observable exposing the breadcrumb hierarchy
readonly breadcrumbs$ = this._breadcrumbs$.asObservable();
constructor(private router: Router) {
this.router.events.pipe(
// Filter the NavigationEnd events as the breadcrumb is updated only when the route reaches its end
filter((event) => event instanceof NavigationEnd)
).subscribe(event => {
// Get the url "/page-1/page-2"
const root = this.router.routerState.snapshot.root;
// Create empty breadcrumb array
const breadcrumbs: Breadcrumb[] = [];
// Make first call to addBreadcrumb function
this.addBreadcrumb(root.firstChild!, [], breadcrumbs);
// Emit the new hierarchy
this._breadcrumbs$.next(breadcrumbs);
});
}
buildDepth(iterations: number, constituentFolders: string[]) {
// This module builds links from the various preceeding folders
// /page-1
// /page-1/page-2
// /page-1/page-2/page-3
var depthStr="";
for (let i=1; i<=iterations; i++)
{
// Add each of the folders to the string
depthStr=depthStr + '/' + constituentFolders[i];
}
return depthStr;
}
private addBreadcrumb(route: ActivatedRouteSnapshot, parentUrl: string[], breadcrumbs: Breadcrumb[]) {
if (route) {
// Get the route url
const routeUrl = parentUrl.concat(route.url.map(url => url.path));
// Break the route into seperate folders /page-1/page-2/ becomes and array of [page-1], [page-2]
let constituentFolders: string[] = new Array();
constituentFolders = routeUrl.toString().split(",")
// I need to add 'home' route at the start to match with the empty '' url.
constituentFolders.splice(0, 0, 'home');
// Iterate over the folders, building breadcrumb links for each part of the url.
// Don't do the last folder as that's the current page.
for(let i = 0; i < (constituentFolders.length); i++){
// Add a breadcrumb link
const breadcrumb = {
label: this.getLabel(constituentFolders[i]),
url: this.buildDepth(i, constituentFolders)
};
breadcrumbs.push(breadcrumb);
// Recursive call to next element in the route.
// pass in the route's first child.
this.addBreadcrumb(route.firstChild!, routeUrl, breadcrumbs);
}
}
}
getLabel(input: string){
// get the label name from the folder taken from the route
// page-1 becomes "page 1"
return input.replace(/-/g, ' ');
}
}
The breadcrumb.component.ts :
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { Breadcrumb } from '@core/models/breadcrumb';
import { BreadcrumbService } from '@core/services/breadcrumb.service';
@Component({
selector: 'app-breadcrumb',
templateUrl: './breadcrumb.component.html',
styleUrls: ['./breadcrumb.component.scss']
})
export class BreadcrumbComponent {
breadcrumbs$: Observable<Breadcrumb[]>;
constructor(breadcrumbService: BreadcrumbService) {
this.breadcrumbs$ = breadcrumbService.breadcrumbs$;
}
}
The breadcrumb.component.html
<ol id="breadcrumbNav" class="breadcrumb-nav">
<li *ngFor="let breadcrumb of (breadcrumbs$ | async)">
<a [href]="breadcrumb.url">{{ breadcrumb.label }}</a>
</li>
</ol>
And finally the CSS just adds the " > " chevrons after the links, but not the last link.
.breadcrumb-nav {
& {
list-style: none;
font-weight: bold;
margin: 0;
padding: 0;
}
li {
display: inline-block; /* display list in a row */
padding-left: 5%;
/* Display chevron before each link */
&:before {
content: " > ";
margin: 0 6px;
}
/* Don't display chevron after the first link */
&:first-child:before {
content: "";
margin: 0;
}
}
}
.breadcrumb-nav a {
color: #45768b;
text-decoration: none;
}
.breadcrumb-nav a:hover {
color: white;
}
Thanks
UPDATE:
I think it's because the default url is localhost:4200/home so there's an additional 'Home' in the first array...
Upvotes: 1
Views: 7253
Reputation: 1141
An alternative solution is - in breadcrumb.service.ts
// I need to add 'home' route at the start to match with the empty '' url.
// The route maybe localhost:4200/home so a 'home' will be in constituentFolders[0]
// We don't want to add a second 'home' then.
if (constituentFolders[0] != 'home') {
constituentFolders.splice(0, 0, 'home');
}
Upvotes: 0
Reputation: 859
Oh I see now! It is the default label + the default path name. The first home
label in the welcome screen is the label set by default in this line:
constituentFolders.splice(0, 0, 'home');
in your service file.
And the second home
is coming from the redirectTo: 'home'
set in the app-routing.module.ts
when the path is empty. You have to remove either of them to have an ideal view of the breadcrumb
, whereby you will have one home
label by default and when you pick page one you will NOT
get a duplication of home
label.
The first approach (the easiest):
1- Go to app-routing.module.ts
2- Instead of this:
const routes: Routes = [
{
path: '',
redirectTo: 'home', // <-- This from where the second label is coming
pathMatch: 'full',
}
]
To ensure that the default URL will be hosting the HomeComponent and you will not duplicate the label you can just do this:
const routes: Routes = [
{
path: '',
component: HomePageComponent
}
]
3- Results:
Thus, I think you do not need to set the home
route in your app-routing.module.ts
but make the default empty URL points to this component.
The second approach is to either add a conditional check in the service or rework the method to not have a hardcoded default value called home
in your breadcrumb component. That's to say that the route tree will be constructed from index 0 dynamically starting from whatever the initial landing screen component route name is. Let me know if the first one is not suitable and you need to rework the service part.
I think the first one resolves your issue.
Here is your updated stackblitz
Upvotes: 1