DEV Community

Cover image for Side Navigation - Angular
karthikeyan
karthikeyan

Posted on • Updated on

Side Navigation - Angular

Hi, I hope you're doing good. Here as an angular UI developers we would have used many frameworks and libraries for minimizing our works or some of us are new to angular UI stream we would have been searching for side navigation with a well-defined style or animation effects but most of us won't get one. So I'm going to spend some time showing how to do that without depending on frameworks and libraries, it is quite simple. Let's jump into creation.

First, create a new project or just create a module in your existing project. To create a module, use the below command, or whatever you use all the time it will make no difference. I've named it side-nav you can use your own.

ng g m side-nav --route side-nav --module app.module
Enter fullscreen mode Exit fullscreen mode

After this, we should be seeing these files created in a folder as mentioned. The size of the files may differ ignore them.

❯ ng g m side-nav --route side-nav --module app.module
CREATE src/app/side-nav/side-nav.component.sass (0 bytes)
CREATE src/app/side-nav/side-nav.component.html (23 bytes)
CREATE src/app/side-nav/side-nav.component.spec.ts (634 bytes)
CREATE src/app/side-nav/side-nav.component.ts (283 bytes)
CREATE src/app/side-nav/side-nav-routing.module.ts (158 bytes)
CREATE src/app/side-nav/side-nav.module.ts (186 bytes)
Enter fullscreen mode Exit fullscreen mode

So from now, I'm going to give the whole code where I'm not going to split the code explain it because it is straightforward and I don't like wasting your time reading this whole post just start developing, and by changing or debugging you will understand each part by yourself.

Now, here's the whole HTML part which goes into side-nav.component.html

<div id="mySidenav" class="sidenav">
    <div class="top-dark-lg">

        <div class="profile" *ngIf="user">
            <img *ngIf="user.pic" [src]="user.pic" alt="profile-pic">
            <h4 *ngIf="user.name">{{ user.name }}</h4>
        </div>
        <div *ngIf="topMenu && topMenu.length > 0" class="menus">
            <div class="menu-item" (click)="navigate(item.link, item.name)"
                [ngClass]="{'active': selectedMenuItem === item.name}" *ngFor="let item of topMenu">
                <span class="title">{{ item.title }}</span>
                <span class="action-icon"> > </span>
            </div>
        </div>
        <div *ngIf="topMenuFlagText" class="option">{{ topMenuFlagText }}</div>

    </div>
    <div *ngIf="bottomMenu && bottomMenu.length > 0" class="down-light-lg-menu">
        <div class="menu-item" (click)="navigate(item.link, item.name)"
            [ngClass]="{'active': selectedMenuItem === item.name}" *ngFor="let item of bottomMenu">{{
            item.title }}</div>
    </div>
    <div class="footer">
        <span *ngIf="legal" class="first">
            {{ legal.title }}
        </span>
        <span *ngIf="version" class="version">{{ version }}</span>
    </div>
</div>

<!-- Use any element to open the sidenav -->
<div class="menu-icon-container" (click)="openNav()">
    <div></div>
    <div></div>
    <div></div>
</div>

<div id="mask"></div>
Enter fullscreen mode Exit fullscreen mode

Next, for styles I have used scss it is same as css but with some extra features. So, this code goes into side-nav.component.scss

.sidenav {
  height: 100%;
  width: 0;
  position: fixed;
  z-index: 1000;
  top: 0;
  left: 0;
  background-color: #f9f9f9;
  overflow-x: hidden;
  border-start-end-radius: 35px;
  transition: 0.3s;
  .top-dark-lg {
    background-color: #141417;
    padding: 10px 1px;
    color: white;
    border-start-end-radius: 35px;
    border-bottom-right-radius: 35px;
    box-shadow: -12px 14px 15px 0px #a2a2a2;
    .profile {
      display: flex;
      flex-direction: column;
      align-items: center;
      margin: 10px;
      img {
        width: 80px;
        height: 80px;
        border-radius: 50%;
      }
      h4 {
        margin: 10px auto;
        letter-spacing: 1px;
      }
    }
    .menus {
      margin: 2px auto 15px;
      .menu-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
        background: #141417;
        padding: 7px 10px;
        border-radius: 5px;
        margin-right: 5px;
        span.title {
          font-weight: 300;
          letter-spacing: 0.3px;
          font-family: sans-serif;
          text-transform: capitalize;
          font-size: 14px;
        }
        span.action-icon {
          color: #006ab7;
        }
        &.active {
          box-shadow: -2px -2px 16px 0px #4c4c4c63, 4px 3px 7px 0px black;
        }
        &:hover {
          cursor: pointer;
        }
      }
    }
    div.option {
      margin: 4px auto 10px;
      text-align: center;
      font-size: 12px;
      color: #006ab7;
    }
  }
  .down-light-lg-menu {
    margin: 20px 10px;
    .menu-item {
      margin: 7px auto;
      padding: 5px 20px;
      border-radius: 5px;
      color: #006ab7;
      &.active {
        background: #f1f1f1;
        color: black;
        box-shadow: inset 2px 2px 7px #9f9f9f, inset -3px -3px 5px #ffffff;
      }
      &:hover {
        background: #e8e8e8;
        cursor: pointer;
      }
    }
  }
  .footer {
    display: flex;
    width: 100%;
    align-items: center;
    justify-content: space-between;
    padding: 5px 20px;
    position: absolute;
    top: 93vh;
    left: 0;
    color: #006ab7;
    .first {
      font-size: 16px;
    }
    .version {
      font-size: 13px;
    }
  }
}

.menu-icon-container {
  width: 35px;
  padding: 5px;
  cursor: pointer;
  div {
    width: 100%;
    height: 4px;
    background-color: black;
    border-radius: 5px;
    margin: 4px 0;
  }
  &:hover div:nth-child(odd) {
    animation: halfload 1s infinite;
    animation-timing-function: ease-in-out;
    -webkit-animation-timing-function: ease-in-out;
  }
  &:hover div:nth-child(even) {
    animation: fullload 1.5s infinite;
    animation-timing-function: ease-in-out;
    -webkit-animation-timing-function: ease-in-out;
  }
}

#mask {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  background-color: rgb(0 0 0 / 31%);
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1;
}

@keyframes halfload {
  0% {
    width: 40%;
  }
  50% {
    width: 100%;
  }
  100% {
    width: 40%;
  }
}

@keyframes fullload {
  0% {
    width: 0%;
  }
  50% {
    width: 100%;
  }
  100% {
    width: 0%;
  }
}

Enter fullscreen mode Exit fullscreen mode

If you're using CSS in your project or you don't understand scss then go to this link and paste the above SCSS in the left pane and click scss to css then get the CSS for this scss in the right pane.

Now the functional part goes here, we so far created a module, HTML, and its CSS. So, all the designing part has been over, now the left out one is just the data and some functions. So the below code goes into side-nav.component.ts

import { Component, ElementRef, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';

@Component({
  host: {
    '(document:click)': 'onClick($event)',
  },
  selector: 'ng-side-navbar',
  templateUrl: './side-nav.component.html',
  styleUrls: ['./side-nav.component.scss'],
})
export class SideNavComponent implements OnInit {
  @Input() user?: { pic?: string; name?: string };
  @Input() topMenu: Array<{ title?: string; name?: string; link?: string }> =
    [];
  @Input() bottomMenu: Array<{ title?: string; name?: string; link?: string }> =
    [];
  @Input() selectedMenuItem?: string;
  @Input() legal?: { title?: string; link?: string };
  @Input() version?: string;
  @Input() topMenuFlagText?: string;
  @Input() noDummyValues?: boolean = false;

  constructor(private _eref: ElementRef, private router: Router) {}

  ngOnInit(): void {
    if (this.noDummyValues) {
      this.setDummyValues();
    }
  }

  setDummyValues(): void {
    if (this.isNullOrUndefined(this.user)) {
      this.user = {
        pic: 'assets/img/profile-pic.jpg',
        name: 'Stylish Tamizhachi',
      };
    }
    if (this.isNullOrUndefined(this.topMenu) || this.topMenu.length === 0) {
      this.topMenu = [{ name: 'myaccount', title: 'My Account' }];
    }
    if (this.isNullOrUndefined(this.topMenuFlagText)) {
      this.topMenuFlagText = 'Do more with you account';
    }
    if (
      this.isNullOrUndefined(this.bottomMenu) ||
      this.bottomMenu.length === 0
    ) {
      this.bottomMenu = [
        { name: 'home', title: 'Home' },
        { name: 'trips', title: 'Trips' },
        { name: 'payment', title: 'Payment' },
        { name: 'manage_subscription', title: 'Manage Subscription' },
        { name: 'help', title: 'Help' },
        { name: 'discussion', title: 'Discussion' },
        { name: 'settings', title: 'Settings' },
      ];
    }
    if (this.isNullOrUndefined(this.legal)) {
      this.legal = { title: 'Legal' };
    }
    if (this.isNullOrUndefined(this.version)) {
      this.version = 'v0.1.32042';
    }
  }

  navigate(link: any, name?: any): void {
    if (!this.isNullOrUndefined(name) && name !== '') {
      this.selectedMenuItem = name;
    }
    if (!this.isNullOrUndefined(link) && link !== '') {
      this.router.navigate([link]);
    }
  }

  openNav() {
    const sideNav = document.getElementById('mySidenav');
    if (sideNav) {
      sideNav.style.width = '250px';
    }
    const main = document.getElementById('mask');
    if (main) {
      main.style.display = 'block';
    }
  }

  closeNav() {
    const sideNav = document.getElementById('mySidenav');
    if (sideNav) {
      sideNav.style.width = '0';
    }
    const main = document.getElementById('mask');
    if (main) {
      main.style.display = 'none';
    }
  }

  onClick(event: any) {
    if (event.target.id === 'mask') {
      this.closeNav();
    }
  }

  isNullOrUndefined(obj: any): boolean {
    return obj === null || obj === undefined;
  }
}

Enter fullscreen mode Exit fullscreen mode

Yup, that's it now we have designed and passed all the data the only part remaining is exporting, importing, and calling in some other components.

  1. Export the SideNavComponent inside side-nav.module.ts
  2. Next, import the SideNavModule in whichever component you need. In most cases, it is used in app.component.ts
  3. Now use it like this.
<ng-side-navbar [noDummyValues]="true"></ng-side-navbar>
Enter fullscreen mode Exit fullscreen mode

So, now start ng serve and see the side navigation with dummy data. Result should be as the below image which shows the dummy data. You could always change the data by passing the data to the inputs we specified in the component that's the whole point of why we created the module.

Result:
result

Also, the whole code is in this git repo AngularComponent which you can always check for clarifications, implementation, and usage.

Sooner or later I have a plan to release a side-nav library especially for angular so when it's done I'll post the updates.

This is my first post and expecting many comments to make myself a little better next time. Please do comment for any mistake or an alternate for this approach. Also, I want to specify Siddharth Chakraborty for the dribbble design in side navigation. I just did the coding part but, he is the designer.

Thanks for reading this post 🙏🏼.

Discussion (0)