You might have noticed a section on websites that show the navigation path of your current location to help you navigate back to any location along the way. the technical term for this is breadcrumbs, you leave a trail/crumb of web links while navigating through the website.
I am not going into the details of the type of breadcrumbs when to use them & best practices in this article, refer to this link if you want an in-depth discussion about breadcrumbs or feel free to do your own research. what I will show you in this article is how you can create a single Angular component that handles all breadcrumb related features.
Prerequisites
Even though advanced knowledge of Angular is not required, understanding of the structure, basic usage of angular CLI and how different components of angular interact will be great. In addition, since we will utilize some of the helper function from rxjs it will be helpful if you are already familiar with the library. other than that We are not going to use any external dependency outside of angular to create this component, instead we will leverage all the events and properties of angular router to achieve our goal.
To get a sense of what to expect at the end of this article check out these stackblitz projects I’ve created for demonstration purposes.
First open the terminal window and navigate to the location where you want to create the angular project and create the project let call it angular-breadcrumb by executing
ng new angular-breadcrumb
Navigate to src/app/app.component.html and replace all the pre-generated HTML code by angular with a router-outlet to display our components while navigating through our application
<router-outlet></router-outlet>
Next, let create all the components we will need for this demonstration at once. we will need to create a navigation tree to show how we can navigate from one component nested inside another component. For that let’s create three-component and call them HomePage, Child, GrandChild they have no use other than a demonstration of breadcrumbs so we will not worry about there content.
ng generate component HomePage
ng generate component ChildChild
ng generate component GrandChild
Go to the routing module configuration at src/app/app-routing.module.ts and update the routes array by defining the routing information to our newly created demonstration components:
const routes: Routes = [
{
path: "",
component: HomePageComponent,
children: [
{
path: "child",
component: ChildComponent,
children: [
{
path: "grand-child",
component: GrandChildComponent
}
]
}
]
}
];
As you can see from the routing configuration our components are nested inside one another and create a tree-like structure. To understand more about Angular’s routing configuration refer to the official documentation.
Next, go to src/app/home-page/home-page.component.html and add a router outlet that will be used to show all of its descendant components in this case ChildComponent. in addition add a button that will be used to navigate to child component when it’s clicked. here is the code:
<p> home page inside view ! </p>
<router-outlet> </router-outlet>
<button type="button" routerLink="/child">Go To Child</button>
We have added a simple text to inform us we are actually viewing homepage component contents. next, we added a router-outlet component, this will be where all the child components will be injected by angular when we navigate through the application. finally, we added a button that will take use to the child component, notice we have a special/angular attribute/directive inside the button element routerLink, which is a directive imported with RouterModule that will let you link to different part of your application, in our case we are linking it to the child component.
Repeat the same process to ChildComponent by updating src/app/child/child.component.html with the following code:
<p> child component inside view </p>
<router-outlet></router-outlet>
<button type="button" routerLink="/child/grand-child"> Go To Grand Child </button>
finally since GrandChild component doesn’t have any child of its own we are not going to put a router-outlet or a navigation button but simply add the following HTML for debugging/confirmation purpose when we are viewing its content.
<p> grand child component inside View! </p>
Great we have successfully created an angular application with three components, it’s not a unicorn application yet, but it will be enough to demonstrate angular routing :).
The moment we have been waiting for, now that we have created the basic structure of angular application with multiple components and functioning navigation between this component, the next step is to create the breadcrumb component that will give the users visual information of their location while they are navigating through the application.
First let’s create a component named BreadCrumbs:
ng generate component BreadCrumbs
This is where the magic starts to happen. As I mentioned previously we are not going to use any external dependency outside of angular to create our reusable breadcrumb component, instead, we are going to make use of event and properties provided by the angular router module.
With that said, go to the newly created breadcrumb component src/app/bread-crmbs/bread-crumbs.component.ts and inject two services provided by @angular/router package namely Router and ActivatedRoute services inside the component constructor:
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router} from "@angular/router";
export class BreadCrumbsComponent implements OnInit {
constructor(private router: Router, private route: ActivatedRoute){}
ngOnInit() {}
}
we have injected the following services
- Router — A service that provides navigation and URL manipulation capabilities. we are mainly interested in the events event of this service to keep track of the router state of our application.
- ActivatedRoute — Used to traverse the
RouterState
tree and extract information from nodes.
Finally lets create a mapping that we will use to record the breadcrumbs of the application navigation. Add this mapping property at the top of the breadcrumbs class.
breadcrumbs: Array<{ label: string; url: string }>;
lets create one more Input property to take the flexibility of our component a little further.
@Input()
public deliminator: string = ">";
This will allow the user to change the deliminator used to separate each breadcrumb to whatever they like by passing it to the component.
Now we are ready to create the bread and butter of our component. Let me show you the code we are going to use first, and then we discuss what each code does afterward. we want to start listening for the router changes as soon as our application starts and our components initialized, for that reason, we will use the angular OnInit life cycle hook to execute our code. copy and paste the following code inside ngOnInit function.
ngOnInit() {
this.router.events
.pipe(
filter(event => event instanceof NavigationEnd)).subscribe(event => {
this.breadcrumbs = [];
let currentRoute = this.route.root,
url = "";
do {
const childrenRoutes = currentRoute.children;
currentRoute = null;
childrenRoutes.forEach(route => {
if (route.outlet === "primary") {
const routeSnapshot = route.snapshot;
url += "/" + routeSnapshot.url.map(segment => segment.path).join("/");
this.breadcrumbs.push({
label:route.snapshot.data.breadCrum,
url: url
});
currentRoute = route;
}
});
} while (currentRoute);
});
}
We have used the router service we have injected inside the constructor and subscribed to its events property which emits routing events each step of the angular router life cycle. next, we used the pipe operator to filter the events emitted from the router to a specific event we want to listen to NavigationEnd which is fired each time a navigation cycle ends.
one thing great about angular router configuration is it enable use to define additional data associated with each route to pass extra information we want to the component, which we will make use of shortly.
Next, we used the activatedRoute service we have injected inside the constructor to get the root/beginning of the router and store that in a temporary variable. Starting from the root of the route we loop through the route tree and save the URL associated with each level of the tree along with the route’s data property found on each route and reading the breadCrumb property we have defined for this purpose. finally, we save each URL with it’s associated breadcrumb value in the mapping we have created previously, breadCrumbs.
Now lets update the src/app/bread-crumbs/bread-crumbs.component.html to display the breadcrumbs generated by the class.
<div>
<span *ngFor="let breadcrumb of breadcrumbs; let last = last"
<small>
<a [routerLink]="breadcrumb.url">{{ breadcrumb.label }}</a> </small>
<span *ngIf="!last">
<small> {{ deliminator }} </small></span>
</span>
</div>
There is nothing complicated about the above HTML, we simply looped through the breadcrumbs generated and assign the URL property to an anchor tags routerLink attribute & display the labels/breadcrumbs adding separator on each iteration.
finally let’s go back to the Router configuration we have created previously and add a data property that holds the breadcrumb value for each route so we can read it inside our BreadCrumb component class.
<app-bread-crumbs [deliminator]="'/'"></app-bread-crumbs>
<router-outlet></router-outlet>
const routes: Routes = [
{
path: "",
component: HomePageComponent,
data: { breadCrumb: "Home"}
children: [
{
path: "child",
component: ChildComponent,
data: { breadCrumb: "child"}
children: [
{
path: "grand-child",
component: GrandChildComponent,
data: { breadCrumb: "grand-child"}
}
]
}
]
}
];
Our component is ready for use all we need to do now is go to src/app/app.component.html and add our component where we want to the breadCrumbs to be displayed.
<app-bread-crumbs [deliminator]="'/'"></app-bread-crumbs>
<router-outlet></router-outlet>
That’s it if you compile and serve your project the breadcrumb component should be working perfectly. you can now add CSS and give it a little bit more swag or add additional features you want for your need. what is great about this component is since all our logic relating to breadcrumb is packed in one place we can make a change to it anytime we want with no headache.