Angular Dynamic Sidebar

Blue and black striped navigation with top level items and children.

This Angular dynamic sidebar illustrates how to populate a navigation with JSON. Something that may become necessary in the event you need to assign user permissions to page content. This would more than likely be accomplished via an API. Hence the dummy data JSON stream. No doubt there’s other ways to develop such a feature. This is but one possible method.

Git Repo

A method that gives us the ability to choose between conventional links or drop down menus with n number of items. In the probable event you have multiple drop downs, toggle them with the Expand All/ Close All button at right top. Use this for desktop, table, or mobile.

Let’s begin with the data. linkText is the top tier text. parentLink is the top tier link. The menu boolean dictates whether the drop down is open or closed. The submenu array contains the drop down menu text and link.

// JSON Driven Menu Selection
[
  { "linkText": "About", "parentLink": "/about", "menu": false, "submenu": [] },
  { linkText": "AngularJS SPA", "parentLink": "/angularjs-spa", "menu": false, "submenu": [] },
  { "linkText": "Codepens", "parentLink": "", "menu": false,
      "submenu": [
        { "childtext": "Link 1",
          "link": "https://codepen.io/UX_Dev/live/5c88e99d0e9a03834dc2d113c24f9daa" },
        { "childtext": "Link 2",
          "link": "https://codepen.io/UX_Dev/pen/LmxqKa" },
        { "childtext": "Link 3",
          "link": "https://codepen.io/UX_Dev/pen/yzejjQ" },
        { "childtext": "Link 4",
          "link": "https://codepen.io/UX_Dev/pen/xoLQrY" },
        { "childtext": "Link 5",
          "link": "https://codepen.io/UX_Dev/pen/2788684ea795e215e9dff54bd5abc66b" }
      ]
  },
  { "linkText": "Navigation", "parentLink": "", "menu": false,
      "submenu": [
        { "childtext": "Pure CSS Menu",
          "link": "https://codepen.io/UX_Dev/pen/VMwxmQ" },
        { "childtext": "User Selection",
          "link": "https://codepen.io/UX_Dev/live/ZXYLyV" },
        { "childtext": "In Page Navigation",
          "link": "https://codepen.io/UX_Dev/pen/JjogojN" },
        { "childtext": "Progress Navigation Bar",
          "link": "https://codepen.io/UX_Dev/pen/xXOapm" }
      ]
  },
  { "linkText": "Angular 8", "parentLink": "", "menu": false,
      "submenu": [
        { "childtext": "Nested Routing",
          "link": "https://stackblitz.com/edit/angular-8-nested-routing" },
        { "childtext": "Modular Routing",
          "link": "https://stackblitz.com/edit/module-to-module-routing" },
        { "childtext": "JSON Line Chart",
          "link": "https://stackblitz.com/edit/line-chart-component-json" }
      ]
  }
]

Developing an Angular Dynamic Sidebar

First stop is to create an interface that represents the payload. SidebarModel does just that. A TypeScript interface is not compiled to JavaScript, since it doesn’t exist in the language. It’s merely a virtual outline solely residing in the TypeScript ecosystem. Used for type checking. Upon transpiling however, they’re removed.

export class SidebarModel {
    linkText: string;
    parentLink: string;
    menu: boolean;
    submenu: { childtext: string; link: string }[];
  }
  • Set the result equal to an empty array with a SidebarModel type.
  • Get the data in the constructor with a reference to the HttpClient.
  • Set result equal to the response.
  • The ddToggle fn toggles the menu boolean by passing the indice.
  • That’s really all there is to it!
  import { HttpClient } from '@angular/common/http';
  import { Component } from '@angular/core';
  import { SidebarModel } from './sidebar.model';
  
  @Component({
    selector: 'app-sidebar',
    templateUrl: './sidebar.component.html',
    styleUrls: ['./sidebar.component.scss'],
  })
  export class SidebarComponent {
    private unsubscribe$ = new Subject<void>();
    result: SidebarModel[] = [];
    expandAll: boolean = false;
    
    constructor(private _http: HttpClient) {
      this._http.get<SidebarModel[]>(
        'path-to-json')
        .pipe(takeUntil(this.unsubscribe$))
        .subscribe((res) => {
        this.result = res;
      });
    }
  
    ddToggle(i: number) {
      this.result[i].menu = !this.result[i].menu;
    }

    toggleAll() {
      this.expandAll = !this.expandAll;
      for (var i = 0; i < this.result.length; i++) {
        this.result[i].menu = this.expandAll;
      }
    }

    ngOnDestroy(){
      this.unsubscribe$.next();
      this.unsubscribe$.complete();
    }
  }
  • Loop over the result containing the imported data.
  • There’s two links—one for items with a submenu, the other without a submenu. Each contains the ddToggle fn. Items that have a parentLink are accommodated.
  • The sub-menu class has a dynamic class tied to the menu boolean value. Following, we loop over the submenu items.
<div id="dynamic-sidebar">
  <span class="expand-all" (click)="toggleAll()">{{
    !expandAll ? 'Expand All' : 'Close All'
  }}</span>
  <div class="wrapper element-shadow">
    <div class="group" *ngFor="let item of result; let i = index">
      <a
        *ngIf="item.submenu.length"
        (click)="ddToggle(i)"
        class="light-blue-bg"
        >{{ item.linkText }}</a
      >
      <a
        *ngIf="!item.submenu.length"
        (click)="ddToggle(i)"
        class="light-blue-bg"
        >{{ item.linkText }}</a
      >
      <div
        *ngIf="item.submenu.length"
        class="caret"
        [ngClass]="{ 'rotate-caret': item.menu }"
      >
        ▶
      </div>
      <div class="sub-menu" [ngClass]="{ 'show-menu': item.menu }">
        <ul *ngFor="let menu of result[i].submenu">
          <li>
            <a>{{ menu.childtext }}</a>
          </li>
        </ul>
      </div>
    </div>
  </div>
</div>

The only thing remaining here is to add HttpClientModule to the module. Not doing this will cause an error. As stated above, there’s numerous ways to accomplish this. Regardless of how it’s done, conceptually, this is a theoretical reference. Feel free to fork this Angular dynamic sidebar project located here. Have a great day!

Be the first to comment

Leave a Reply

Your email address will not be published.


*