Build a dynamic navigation bar with Angular Material
Developers love shortcuts.
And one way to shortcut your dev time is to dynamically render the navigation bar links in your Angular app straight from your routes.
That way, any time you create or delete routes those changes are automatically reflected in your navigation bar.
Today we're going to learn how to do this with Angular Material.
Let's get into this!
1. Create the navigation bar
First we'll need to create our navigation bar. We'll use an Angular Material schematic to automatically do this for us.
ng generate @angular/material:navigation navbar
This will generate a component called navbar
that will display in our app like this.
You'll notice that we have 3 links on the side of our navigation bar.
The goal is to render these links dynamically from a route config.
2. Declaring our routes
Now we'll go to the app.routes.ts
file and declare all our parent routes for our dashboard.
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
title: 'Home',
loadComponent: () => import('./home/home.component').then(m => m.HomeComponent),
},
{
path: 'settings',
title: 'Settings',
loadComponent: () => import('./settings/settings.component').then(m => m.SettingsComponent),
},
{
path: 'logout',
title: 'Logout',
loadComponent: () => import('./logout/logout.component').then(m => m.LogoutComponent),
}
];
3. Dynamically rendering the app routes
Now back to our navbar.component.ts
file.
We'll first import our routes.
import { Component, inject } from '@angular/core';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { AsyncPipe } from '@angular/common';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatListModule } from '@angular/material/list';
import { MatIconModule } from '@angular/material/icon';
import { Observable } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { routes } from '../app.routes';
import { RouterModule } from '@angular/router';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrl: './navbar.component.scss',
standalone: true,
imports: [
MatToolbarModule,
MatButtonModule,
MatSidenavModule,
MatListModule,
MatIconModule,
AsyncPipe,
RouterModule
]
})
export class NavbarComponent {
routes = routes;
private breakpointObserver = inject(BreakpointObserver);
isHandset$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.Handset)
.pipe(
map(result => result.matches),
shareReplay()
);
}
And then we'll use a @for loop to dynamically render all our routes inside of the navigation bar.
<mat-sidenav-container class="sidenav-container">
<mat-sidenav #drawer class="sidenav" fixedInViewport
[attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
[mode]="(isHandset$ | async) ? 'over' : 'side'"
[opened]="(isHandset$ | async) === false">
<mat-toolbar>Menu</mat-toolbar>
<mat-nav-list>
@for (item of routes; track $index) {
<a mat-list-item [routerLink]="item.path" routerLinkActive="active"> {{ item.title }}</a>
}
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content>
<mat-toolbar color="primary">
@if (isHandset$ | async) {
<button
type="button"
aria-label="Toggle sidenav"
mat-icon-button
(click)="drawer.toggle()">
<mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
</button>
}
<span>material3-playground</span>
</mat-toolbar>
<!-- Add Content Here -->
</mat-sidenav-content>
</mat-sidenav-container>
And now our navigation bar will dynamically show all the parent routes inside of the app.routes.ts
file.
4. Adding Icons
What if we wanted to add icons to our dynamic navigation bar items?
First, we'll go back to the app.routes.ts
and use the data option to set our icons for the different parent routes.
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
title: 'Home',
data: {
icon: 'home'
},
loadComponent: () => import('./address-form/address-form.component').then(m => m.AddressFormComponent),
},
{
path: 'settings',
title: 'Settings',
data: {
icon: 'settings'
},
loadComponent: () => import('./address-form/address-form.component').then(m => m.AddressFormComponent),
},
{
path: 'logout',
title: 'Logout',
data: {
icon: 'logout'
},
loadComponent: () => import('./address-form/address-form.component').then(m => m.AddressFormComponent),
}
];
And then in the navbar.component.html
file we'll dynamically display the icons.
<mat-sidenav-container class="sidenav-container">
<mat-sidenav #drawer class="sidenav" fixedInViewport
[attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
[mode]="(isHandset$ | async) ? 'over' : 'side'"
[opened]="(isHandset$ | async) === false">
<mat-toolbar>Menu</mat-toolbar>
<mat-nav-list>
@for (item of routes; track $index) {
<a mat-list-item [routerLink]="item.path" routerLinkActive="active">
@if (item.data && item.data['icon']) {
<mat-icon matListItemIcon>{{ item.data['icon'] }}</mat-icon>
}
<div matListItemTitle>{{item.title}}</div>
</a>
}
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content>
<mat-toolbar color="primary">
@if (isHandset$ | async) {
<button
type="button"
aria-label="Toggle sidenav"
mat-icon-button
(click)="drawer.toggle()">
<mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
</button>
}
<span>material3-playground</span>
</mat-toolbar>
<!-- Add Content Here -->
</mat-sidenav-content>
</mat-sidenav-container>
And that, my Angular friend, is how to dynamically render your parent routes inside an Angular Material navigation bar.
Till next time.
Daniel Kreider