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.

angular-material-navbar-demo

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.

angular-material-dynamic-routes

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.

signature

Daniel Kreider