Angular Material – Navbar with Responsive Side Menu

Table of Contents
Overview
This post is an example of how to build an Angular Material navigation bar with a responsive side menu drawer. The side menu will also highlight the options based on the Router Path.
Source CodeNode-Sass
Warning about the code from GitHub: You may need to run the following NPM commands if you get a node-sass incompatibility error.
npm uninstall node-sass
npm install node-sass@4.14.1
ng s -o
Create Angular Application and Install Material
ng new material-layout-app
ng add @angular/material
Add a couple of components such as home and profile.
ng g c components/home-page
ng g c components/profile
In the GitHub code example there is a material.module.ts file which has all of the imports for Material.
Remember when creating a new component you have to add ng g c component-name –module=app when using a separate module like material.module.ts. Click here to see an example of a separate module for material imports.
Layout Component
Generate a component for the layout.
ng g c material-layout
In the material-layout.component.ts file import the following libraries.
import { Component, OnInit, HostBinding } from '@angular/core';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { Observable } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { Router } from '@angular/router';
import { MatSidenav } from '@angular/material/sidenav';
import { MatSnackBar } from '@angular/material/snack-bar';
Optional – This example includes using a snackbar for small alerts such as picking a menu item.
In the example from GitHub, there is an environment variable set to show the side menu (drawer) open or closed.
In the gitHub example there is a material.module.ts file which has all of the imports for Material.
Remember when creating a new component you have to add ng g c component-name –module=app when using a separate module like material,module.ts.
Layout Component – TypeScript
All of the TypeScript for the material-layout.component.ts file:
import { Component, OnInit, HostBinding } from '@angular/core';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
import { Observable } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { Router } from '@angular/router';
import { MatSidenav } from '@angular/material/sidenav';
import { MatSnackBar } from '@angular/material/snack-bar';
@Component({
selector: 'material-layout',
templateUrl: './material-layout.component.html',
styleUrls: ['./material-layout.component.scss']
})
export class MaterialLayoutComponent implements OnInit {
public loading: boolean;
public isAuthenticated: boolean;
public title: string;
public isBypass: boolean;
public mobile: boolean;
public isMenuInitOpen: boolean;
constructor(private breakpointObserver: BreakpointObserver,
private router: Router,
private _snackBar: MatSnackBar) { }
private sidenav: MatSidenav;
public isMenuOpen = true;
public contentMargin = 240;
get isHandset(): boolean {
return this.breakpointObserver.isMatched(Breakpoints.Handset);
}
ngOnInit() {
this.isMenuOpen = true; // Open side menu by default
this.title = 'Material Layout Demo';
}
ngDoCheck() {
if (this.isHandset) {
this.isMenuOpen = false;
} else {
this.isMenuOpen = true;
}
}
public openSnackBar(msg: string): void {
this._snackBar.open(msg, 'X', {
duration: 2000,
horizontalPosition: 'center',
verticalPosition: 'top',
panelClass: 'notif-error'
});
}
public onSelectOption(option: any): void {
const msg = `Chose option ${option}`;
this.openSnackBar(msg);
/* To route to another page from here */
// this.router.navigate(['/home']);
}
}
Mobile Device Detection
This following function automatically collapses the side menu (drawer) in a mobile device. The code below determines if this is a mobile device. Just look at breakpointObserver and you will get back a lot of use information about the device that is viewing your site.
get isHandset(): boolean {
return this.breakpointObserver.isMatched(Breakpoints.Handset);
}
Add an ngDoCheck() to collapse or expand the side menu if resized.
ngDoCheck() {
if (this.isHandset) {
this.isMenuOpen = false;
} else {
this.isMenuOpen = true;
}
}
Menu Item Highlighting
To highlight the menu items using the routerLinkActive property in the anchor tag.
<mat-nav-list >
<a [routerLink]="['/home']" mat-list-item href="#" routerLinkActive="active">
<mat-icon routerLinkActive="active-icon">home</mat-icon><span class="ml-2" > Welcome </span></a>
<a [routerLink]="['/profile']" mat-list-item href="#" routerLinkActive="active">
<mat-icon routerLinkActive="active-icon">settings</mat-icon>
<span class="ml-2" > Profile Settings</span></a>
</mat-nav-list>
The corresponding CSS that is used to change the link from black to dark red.
.active {
font-weight: bold;
background-color: #CFCFCF;
color: darkred;
}
.active:active {
font-weight: bold;
background-color: lightpink;
}
Layout Component – CSS
Add the following to layout CSS material-layout.component.html component
.main {
overflow-y: hidden;
overflow-x: hidden;
}
.sidenav-container {
height: 92%;
position: relative;
}
.sidenav-content {
width: 100%;
}
.sidenav {
width: 250px;
}
.sidenav .mat-toolbar {
background: #fff;
color: #fff;
}
.toolbar-icon {
padding: 0 0px;
}
.sidenavContainer {
height: 100%;
}
.toolbar-spacer {
flex: 1 1 auto;
}
.active {
font-weight: bold;
background-color: #CFCFCF;
color: darkred;
}
.active:active {
font-weight: bold;
background-color: lightpink;
}
.active-icon {
color: red;
}
Layout Component – HTML
Open material-layout.component.html and copy/paste the following:
<mat-toolbar color="primary">
<mat-toolbar-row>
<button mat-icon-button (click)="drawer.toggle();"><mat-icon routerLinkActive="active-icon">menu</mat-icon></button>
<span>{{title}}</span>
<span class="toolbar-spacer "></span>
</mat-toolbar-row>
</mat-toolbar>
<mat-sidenav-container class="sidenav-container" >
<mat-sidenav
#drawer
class="sidenav"
[attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'"
[mode]="(isHandset$ | async) ? 'over' : 'side'"
[opened]="isMenuOpen">
<mat-nav-list >
<a [routerLink]="['/home']" mat-list-item href="#" routerLinkActive="active"><mat-icon routerLinkActive="active-icon">home</mat-icon><span class="ml-2" > Welcome </span></a>
<a [routerLink]="['/profile']" mat-list-item href="#" routerLinkActive="active"><mat-icon routerLinkActive="active-icon">settings</mat-icon><span class="ml-2" > Profile Settings</span></a>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content [ngStyle]="{ 'margin-left.px': contentMargin }" >
<div class="main">
<router-outlet #outlet="outlet"></router-outlet>
</div>
</mat-sidenav-content>
</mat-sidenav-container>
The router <router-outlet> is wrapped with the Material side navigation tag. <mat-sidenav-container></mat-sidenav-container>. Since it is inside the container tags, the content will be responsive to a resize of the menu or change in the drawer state being open or closed.
<mat-sidenav-content [ngStyle]="{ 'margin-left.px': contentMargin }" >
<div class="main">
<router-outlet #outlet="outlet"></router-outlet>
</div>
</mat-sidenav-content>
Finally – Render the Layout in app.component.ts
Open app.component.html
Remove the html that is in the new Angular App and add the following
<app-material-layout></app-material-layout>
Optional – Adding a SnackBar to Display Chosen Menu Item
In the GitHub demo there are a couple of extras such as a drop down side menu on the upper right that triggers a Material Snackbar and has the router set up.
Import the snack bar and declare it in the constructor.
Import Libraries
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from '@angular/router';
Updates to the Constructor
Also add the router in addition to the snackbar and Breakpoint observer.
constructor(private breakpointObserver: BreakpointObserver,
private router: Router,
private _snackBar: MatSnackBar) { }
Inside the material-layout.component.ts add the following below the ngDoCheck(),
The router has been disabled, but I left it there just in case you want to use the menu options to navigate to another page.
public openSnackBar(msg: string): void {
this._snackBar.open(msg, 'X', {
duration: 2000,
horizontalPosition: 'center',
verticalPosition: 'top',
panelClass: 'notif-error'
});
}
public onSelectOption(option: any): void {
const msg = `Chose option ${option}`;
this.openSnackBar(msg);
/* To route to another page from here */
// this.router.navigate(['/home']);
}
Updates to material-layout.component.html
The following html goes between the <mat-toolbar-row> tags after the <span>{{title}}</span>. The <span class=”toolbar-spacer “></span> uses CSS defined in the material-layout.component.scss file pushes everything after to the right side.
<span class="toolbar-spacer "></span>
<button mat-icon-button [matMenuTriggerFor]="menu" aria-label="Example icon-button with a menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="onSelectOption('1')">
<mat-icon>wb_sunny</mat-icon>
<span>Option 1</span>
</button>
<button mat-menu-item (click)="onSelectOption('2')">
<mat-icon>lens</mat-icon>
<span>Option 2</span>
</button>
<button mat-menu-item (click)="onSelectOption('3')">
<mat-icon>brightness_3</mat-icon>
<span>Option 3</span>
</button>
</mat-menu>
Adding the Routes
Setup the router in app-routing.module.ts.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomePageComponent } from './components/home-page/home-page.component';
import { ProfileComponent } from './components/profile/profile.component';
const routes: Routes = [
{
path: '',
component: HomePageComponent
},
{
path: 'home',
component: HomePageComponent
},
{
path: 'profile',
component: ProfileComponent
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
You must be logged in to post a comment.