DEV Community

Cover image for How to create a responsive sidebar and mini navigation with Material Angular
David Ihl
David Ihl

Posted on

How to create a responsive sidebar and mini navigation with Material Angular

Table of Contents


Introduction

Since I am in the process of onboarding with Angular using the Angular Material framework in particular, like many developers before me, I found myself pondering how to create a responsive side navigation. This navigation should push the main content to the side on desktops, yet overlap it on mobile resolutions.

While this task seemed fairly easy, I also aimed to ensure that the sidenav wouldn't completely disappear on tablet and desktop resolutions. Instead, I wanted to display a collapsed version using only icons.

Mobile Behaviour

Animated behaviour of the angular application when clicking on the menu button on mobile screens

Desktop/Tablet Behaviour

Animated behaviour of the angular application when clicking on the menu button on desktop screens

This stretch goal was what inspired me to write this post. I wanted to share not only the solution I came up with but also because some of the highly ranked suggested solutions seemed outdated or not elegant, as they sometimes heavily rely on overwriting classes of the UI component library.

Repository / Live Demo
You can find and fork my example repository at my Github profile or try it out live on Netlify.

Prequisites

For this tutorial, you should prepare everything you need to create and develop an Angular application. For more information, please visit the Angular documentation. I also recommend having a basic understanding of modern Angular application architecture since I won't delve too deeply into the specifics of the used directives and components, but rather provide links to the documentation.

Step 1: Setup Angular

Create a new Angular application

 ng new material-responsive-sidenav
Enter fullscreen mode Exit fullscreen mode

While it's not particularly crucial for this demonstration, I opted to include Angular routing and chose to use SCSS.

Step 2: Add Angular Material

After all npm packages have been installed, we can add the Material framework to our Angular application by navigating toour newly created app directory

ng add @angular/material
Enter fullscreen mode Exit fullscreen mode

After confirming, you are free to choose a preset theme or create a custom one. Since this guide does not focus on theming in Angular, I opted for a prebuilt theme. Additionally, I set up global typography styles and included & enabled animations.

Once the import is complete, you can start the application with

ng serve
Enter fullscreen mode Exit fullscreen mode

The application will by default be available on http://localhost:4200/

Step 3: Import required components

Since we are going to use Angular components, let's clean up the app.component.html file within the app-root directory and modify the app.module.ts file to include the required Material components.

import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatToolbarModule } from '@angular/material/toolbar';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
    MatIconModule,
    MatButtonModule,
    MatToolbarModule,
    MatSidenavModule,
    MatListModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Enter fullscreen mode Exit fullscreen mode

What is happening here:

Since we are using components from Angulars Material framework
these imports are required:

  1. Material Toolbar for the appbar on top
  2. Material Icon and Material Button for the menu icon botton we are going to implement
  3. Material Sidnav for our sidenav.
  4. Material List to wrap our navigation items nicely.

By adding the imports in the global app.module.ts, they will be available in every (non-standalone) component going forward without the need to import them individually. Since we won't be adding an additional component in this tutorial, I chose this approach for simplicity. However, it's worth noting that you could also create separate components for the sidenav and appbar if desired.

Step 4: Implement the components

After importing all the required Angular Material components for now, we can start building the template by editing the app.component.html file.

Adding the Toolbar

<mat-toolbar color="primary">
  <button mat-icon-button aria-label="Menu icon">
    <mat-icon>menu</mat-icon>
  </button>
  <h1>Responsive Material Sidenavigation</h1>
</mat-toolbar>
Enter fullscreen mode Exit fullscreen mode

I chose to implement a simple Material toolbar with just a button for toggling our menu as well as an app title.

Adding the Sidenavigation

<mat-sidenav-container autosize>
  <mat-sidenav [opened]="true" mode="side">
    <mat-nav-list>
      <a mat-list-item>
        <span class="entry">
          <mat-icon>house</mat-icon>
          <span>Dashboard</span>
        </span>
      </a>
    </mat-nav-list>
  </mat-sidenav>
  <mat-sidenav-content>
    <h2>Content</h2>
    <router-outlet></router-outlet>
  </mat-sidenav-content>
</mat-sidenav-container>
Enter fullscreen mode Exit fullscreen mode

For the Material Sidenavigation, I enabled autosize to resize the container during toggling. This will make sure the main content is moving accordingly.

[opened]="true" and mode="side" are used as placeholders for now, as we will add the functionality moving forward. If you're curious about the square brackets, you can find more information about Angular Property Binding here.

Inside the mat-sidenav component, I am creating a mat-nav-list and adding the mat-list-item property to our navigation link. This wrapping helps structure the layout and utilize the nice hover and click ripple effects when clicking. The a link itself consists of another Material Icon and a menu text.

The mat-sidenav-content component contains a placeholder h2 and also utilizes Angular's router-outlet in preparation for possible routing to different pages/components.

If you are coding along with me, you will notice that the result is not quite satisfying yet. Let's enhance the visual appeal by editing the app.component.scss file.

h1 {
    padding: 0 1rem;
}

h2 {padding: 1rem;}

mat-toolbar{
    position:fixed;
    top:0;
    z-index: 2;
}

mat-sidenav-container {
    height:100%;
}

// Move the content down so that it won't be hidden by the toolbar
mat-sidenav {
    padding-top: 3.5rem;
    @media screen and (min-width: 600px) {
      padding-top: 4rem;
    }

    .entry{
        display: flex;
        align-items: center;
        gap: 1rem;
        padding:0.75rem;
    }
  }

// Move the content down so that it won't be hidden by the toolbar
mat-sidenav-content{
    padding-top: 3.5rem;
    @media screen and (min-width: 600px) {
        padding-top: 4rem;
    }
}
Enter fullscreen mode Exit fullscreen mode

What is happening here

As I'm utilizing the default behavior of Material's toolbar, it's important to note that its height changes based on the screen size, whether it's wider or smaller than 600px. Therefore, to keep our appbar fixed at the top, I'm adding paddingto both the sidenav and content sections depending on the screen size.

At this stage, our Angular application appears like this:

Styled application using Materials toolbar, side navigation and icons including hover and click animations

Step 5: Adding responsiveness

Let's return to the sidenavcomponent where we previously added placeholders for the opened state as well as the mode it is using.

Since we want to change the mode based on screensize, we will replace the placeholder in the app.component.html file and implement a BreakpointObserver which is a handy feature of the Material Component Development Kit. To do this, we will modify the app.component.ts file as followed:

import { BreakpointObserver } from '@angular/cdk/layout';
import {
  Component,
  ViewChild,
} from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'material-responsive-sidenav';
  @ViewChild(MatSidenav)
  sidenav!: MatSidenav;
  isMobile= true;


  constructor(private observer: BreakpointObserver) {}

  ngOnInit() {
    this.observer.observe(['(max-width: 800px)']).subscribe((screenSize) => {
      if(screenSize.matches){
        this.isMobile = true;
      } else {
        this.isMobile = false;
      }
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

What is happening here:

At the very beginning, we import the BreakpointObserver along with the ViewChild and MatSidenav modules, as we will need these for implementing the observer.

Within the component class, we introduce the ViewChild property and assign it to the sidenav. This step is taken to monitor the behavior of the side navigation.

Following the declaration of sidenav and the creation of a variable that indicates whether a mobile resolution is being used (we will revisit this later), we initialize the BreakpointObserver service using the constructor.

Next, we utilize the NgOnInit lifecycle hook to influence the behavior of the sidenav component and to toggle the isMobile state between true and false.

Before proceeding to the next step, we need to address the changes required in our app.component.html file by using conditional property binding:

 <mat-sidenav [mode]="isMobile ? 'over' : 'side'" [opened]="isMobile ? 'false' : 'true'">
    // ...
  </mat-sidenav>
Enter fullscreen mode Exit fullscreen mode

Step 6: Toggle the menu

Continuing the editing of the app.component.ts, we proceed to create a function named toggleMenu where we will utilize the isMobile variable.

  toggleMenu() {
    if(this.isMobile){
      this.sidenav.toggle();
    } else {
      // do nothing for now
    }
  }
Enter fullscreen mode Exit fullscreen mode

To finaly open and close the navigation on mobile resolutions, we modify the app.component.html and add an event listener to the button in our appbar.

<button mat-icon-button aria-label="Menu icon" (click)="toggleMenu()">
    <mat-icon>menu</mat-icon>
  </button>
Enter fullscreen mode Exit fullscreen mode

At this point, our appbar button will open and close the side navigation using the built-in method of the Material sidnav component.

Step 7: Collapsing the navigation

The only remaining task is to add the collapsing behavior to our sidenav component. To achieve this, we will initialize another variable in our app.component.ts file called isCollapsed and incorporate it into our toggle function.

Adding the Variable

isCollapsed = true;
Enter fullscreen mode Exit fullscreen mode

Complete the Toggle Function

  toggleMenu() {
    if(this.isMobile){
      this.sidenav.toggle();
      this.isCollapsed = false; // On mobile, the menu can never be collapsed
    } else {
      this.sidenav.open(); // On desktop/tablet, the menu can never be fully closed
      this.isCollapsed = !this.isCollapsed;
    }
  }
Enter fullscreen mode Exit fullscreen mode

To complete this step, the final task is to implement a conditional rendering in our app.component.html template.

Add Conditional Rendering

<a mat-list-item>
   <span class="entry">
      <mat-icon>house</mat-icon>
      <span *ngIf="!isCollapsed">Dashboard</span>
   </span>
</a>
Enter fullscreen mode Exit fullscreen mode

What is happening here:

We have successfully completed the implementation of interactivity for our responsive menu. The decision was made to enable toggling only on mobile screens. On larger resolutions, we ensure that the menu remains open, but we instead toggle a variable that is utilized to hide the text, displaying only the icon.

Bonus Step: Conditional classes

In case you want to implement a specific styling based on wether the side navigation is collapsed or expaned in bigger resolutions, with ngClass you can take advantage of another built-in directive of Angular to apply different styles, e.g. a specific width.

Apply ngClass directive

  <mat-sidenav [ngClass]="!isCollapsed ? 'expanded' : ''" [mode]="isMobile ? 'over' : 'side'" [opened]="isMobile ? 'false' : 'true'">
    // ...
  </mat-sidenav>
Enter fullscreen mode Exit fullscreen mode

Modify stylesheet

.expanded {
    width: 250px;   
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)