How to create a responsive sidebar and mini navigation with Material Angular
Tags: angular, beginners, frontend
Published: 2023-08-28 14:43:25

###Table of Contents

- [Prequisites](#Prequisites)
- [Step 1: Setup Angular](#Setup-Angular)
- [Step 2: Add Angular Material](#Add-Angular-Material)
- [Step 3: Import required components](#Import-required-components)
- [Step 4: Implement the components](#Implement-the-components)
- [Step 5: Adding responsiveness](#Adding-responsiveness)
- [Step 6: Toggle the menu](#Toggle-the-menu)
- [Step 7: Collapsing the navigation](#Collapsing-the-navigation)
- [Bonus: Conditional classes](#Conditional-classes)

---


#Introduction

Since I am in the process of onboarding with [Angular](https://angular.io) using the [Angular Material framework](https://material.angular.io/) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ufo3957znojwmqxx374x.gif)


> ###Desktop/Tablet Behaviour
![Animated behaviour of the angular application when clicking on the menu button on desktop screens](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4kntdi2gdhgj5vh467uh.gif)

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](https://github.com/Davidihl/angular-material-responsive-sidenav/) or try it out [live on Netlify](https://main--thunderous-ganache-2d8d02.netlify.app/).

#Prequisites <a name="Prequisites"></a>
For this tutorial, you should prepare everything you need to create and develop an Angular application. For more information, please visit the [Angular documentation](https://angular.io/guide/setup-local). 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 <a name="Setup-Angular"></a>
Create a new Angular application

```bash
 ng new material-responsive-sidenav
```
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 <a name="#Add-Angular-Material"></a>
After all npm packages have been installed, we can add the Material framework to our Angular application by navigating toour newly created app directory

```bash
ng add @angular/material
```
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

```bash
ng serve
```
The application will by default be available on `http://localhost:4200/`

#Step 3: Import required components <a name="Import-required-components"></a>
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.

```javascript
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 { }

```
###What is happening here:
Since we are using [components from Angulars Material framework
](https://material.angular.io/components/categories) 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 <a name="Implement-the-components"></a>
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**
```html
<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>
```

I chose to implement a simple [Material toolbar](https://material.angular.io/components/toolbar/overview) with just a button for toggling our menu as well as an app title.

**Adding the Sidenavigation**
```html
<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>
```
For the [Material Sidenavigation](https://material.angular.io/components/sidenav/overview), 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](https://angular.io/guide/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 ](https://angular.io/guide/router-reference)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.

```css
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;
    }
}
```
###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 `padding`to 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mutp5q73m84xe4xnr8my.png)

#Step 5: Adding responsiveness <a name="Adding-responsiveness"></a>
Let's return to the `sidenav `component 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](https://material.angular.io/cdk/layout/overview). To do this, we will modify the `app.component.ts` file as followed:

```javascript
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;
      }
    });
  }
}
```
###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](https://angular.io/api/core/ViewChild) 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](https://angular.io/guide/lifecycle-hooks) 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:

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

#Step 6: Toggle the menu <a name="Toggle-the-menu"></a>
Continuing the editing of the `app.component.ts`, we proceed to create a function named `toggleMenu` where we will utilize the `isMobile` variable.

```javascript
  toggleMenu() {
    if(this.isMobile){
      this.sidenav.toggle();
    } else {
      // do nothing for now
    }
  }
```

To finaly open and close the navigation on mobile resolutions, we modify the `app.component.html` and add an [event listener](https://angular.io/guide/event-binding) to the button in our appbar.

```html
<button mat-icon-button aria-label="Menu icon" (click)="toggleMenu()">
    <mat-icon>menu</mat-icon>
  </button>
```
At this point, our appbar button will open and close the side navigation using the [built-in method](https://material.angular.io/components/sidenav/api#MatDrawer) of the Material `sidnav` component.

#Step 7: Collapsing the navigation <a name="Collapsing-the-navigation"></a>
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**
```javascript
isCollapsed = true;
```

**Complete the Toggle Function**
```javascript
  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;
    }
  }
```
To complete this step, the final task is to implement a conditional rendering in our `app.component.html` template.

**Add Conditional Rendering**
```html
<a mat-list-item>
   <span class="entry">
      <mat-icon>house</mat-icon>
      <span *ngIf="!isCollapsed">Dashboard</span>
   </span>
</a>
```

###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 <a name="Conditional-classes"></a>
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](https://angular.io/api/common/NgClass#description) to apply different styles, e.g. a specific width.

**Apply ngClass directive**
```html
  <mat-sidenav [ngClass]="!isCollapsed ? 'expanded' : ''" [mode]="isMobile ? 'over' : 'side'" [opened]="isMobile ? 'false' : 'true'">
    // ...
  </mat-sidenav>
```

**Modify stylesheet**
```css
.expanded {
    width: 250px;   
}
```