Hamburger Menu Using Angular Material

A hamburger menu is represented by a three horizontal lines icon which, when clicked, displays the navigation menu for the web application. This menu has been the common choice for web applications as it normally features a collapsible menu that makes it the best fit for smaller screens such as smart phones and tablets.

In this post, we will take a look at implementing a hamburger menu using Angular Material. Let’s start by creating an Angular project as described in my previous article. Since we will be building this on top of Angular Material, add the library by running the following in Node.js command prompt:

ng add @angular/material

There will be prompts where you are to select from a few options on the list. Once you are done installing the Angular Material library, add a new module that will contain all the imported modules from the Material library:

ng generate module app-ui --flat

In this app-ui.module.ts file, we have to do an import and export of the modules that we are going to use. In this post, we are going to use three modules from Angular Material – MatSidenavModule, MatIconModule, and MatButtonModule:

import { NgModule } from '@angular/core';
import { MatSidenavModule, MatIconModule, MatButtonModule } from '@angular/material';

const uiModules = [
  MatSidenavModule,
  MatIconModule,
  MatButtonModule
];

@NgModule({
  imports: uiModules,
  exports: uiModules
})
export class AppUiModule { }

Then, let’s import the AppUiModule into our main AppModule:

// more imports
import { AppUiModule } from './app-ui.module';

@NgModule({
  imports: [
    // other imports
    AppUiModule
  ],
  // other attributes
})
export class AppModule { }

Open the app.component.html. Let’s start off simple by creating the hamburger menu button on our header:

<div class="page-container">
  <header>
    <div>
      <button mat-button class="menu-button">
        <mat-icon>menu</mat-icon>
      </button>
  
      <a routerLink="/">
        <span class="app-title">Hamburger Menu Demo</span>
      </a>
    </div>
  </header>
</div>

We have a few styles here, so open app.component.scss and add the following styles:

.page-container {
    height: 100%;
    background: #fafafa;
    color: #000;

    display: flex;
    flex-direction: column;

    a {
        color: inherit;
        text-decoration: none;
    }

    header {
        background: #3f51b5;
        color: #fff;

        padding: 18px 24px;
        
        font-size: 14px;
        font-weight: bold;

        display: flex;
        justify-content: space-between;

        .menu-button {
            margin-right: 24px;
            min-width: 0;
            padding: 0;
            line-height: normal;
        }

        .app-title {
            padding: 0 3px;
        }
    }
}

Here, we have a page container block that simplifies the layout of our page. The reason for having this block is that in Angular, it inserts additional elements for your components (in this case, app-root element) which messes up with our layout. The page container block is there to make our layout expand 100% vertically so that it consumes the entire page (i.e. height = 100%). In a non-Angular application, this container is redundant and unnecessary.

Our button element has the mat-button attribute to indicate that we are using the Angular Material button design and behavior. Then, inside our button is the hamburger menu icon using Material icon. The list of Material icons can be found on this link. To use an icon, look for it on the list. It shall have a name as a caption under the icon. This will be the text placed inside the mat-icon tag:

<mat-icon>menu</mat-icon>

When you run the application, you shall have a similar output like the one below:

Now, let create the side navigation menu. We will be using the sidenav component:

<mat-sidenav-container class="sidenav-container">
  <mat-sidenav #sidenav autoFocus="false" mode="over" [(opened)]="isMenuOpen" class="sidenav">
    <a mat-button routerLink="/page1">Page 1</a>
    <a mat-button routerLink="/page2">Page 2</a>
    <a mat-button routerLink="/page3">Page 3</a>
    <a mat-button routerLink="/page4">Page 4</a>
    <a mat-button routerLink="/page5">Page 5</a>
  </mat-sidenav>
  <mat-sidenav-content>
    <main>
      <router-outlet></router-outlet>
    </main>
  </mat-sidenav-content>
</mat-sidenav-container>

We add the following styles for our sidenav on the app.component.scss within the page container block:

.sidenav-container {
    flex: 1;

    .sidenav {
        min-width: 240px;

        a {
            display: block;
            padding: 6px 24px;
            width: 100%;
            text-align: left;
        }
    }

    main {
        padding: 18px 24px;
    }
}

Our sidenav container contains the sidenav and the main content. The sidenav holds our side navigation menu:

  • A sidenav variable has been declared to refer to our sidenav component, which we will use to toggle our sidenav when we click the hamburger menu.
  • Setting autofocus to false prevents focus to the menu navigation links when the sidenav appears.
  • There can be three modes available for use – over, push, or side:
    • over places the sidenav over the main content on an overlay
    • push would push the main content (with an overlay) to the right to give space to the sidenav
    • side is similar to push but there is no overlay on the main content making its contents workable and enabled.
  • opened is two-way bounded to isMenuOpen which would indicate to show or hide our sidenav

Now, let’s make a little adjustment on our hamburger menu button to handle the click event so that it toggles our side navigation component:

<button mat-button class="menu-button" (click)="sidenav.toggle()">
  <mat-icon>{{ isMenuOpen ? 'menu_open' : 'menu' }}</mat-icon>
</button>

On our click event, we simply call the toggle method of the sidenav component. In addition, we also modify the icon to display so that if our sidenav is open, we use menu_open icon; menu, otherwise.

Next, let’s add the isMenuOpen property to the app.component.ts:

public isMenuOpen: boolean = false;

Now, run the application and toggle the sidenav menu. You would be able to see a similar behavior like below:

This looks good, except that when you click on a navigation menu link, the sidenav stays visible. Let’s fix that!

Add an event handler on our sidenav component for the click event:

<mat-sidenav 
  #sidenav 
  autoFocus="false" 
  mode="over" 
  [(opened)]="isMenuOpen" 
  class="sidenav" 
  (click)="onSidenavClick()"
  >
  <!-- more code here -->
</mat-sidenav>

Then, on our TypeScript file:

public onSidenavClick(): void {
  this.isMenuOpen = false;
}

Run the application and try clicking on one of the menu. The sidenav should automatically close after a menu selection.