Bootstrap 4 navbar that can render itself as a function of multiple states

Aurelia Skeleton Navigation nav-bar is a “view only” component created to support a single state - active, where currently active navbar menu item is rendered differently than others. This is achieved in this repeat.for loop:

<ul class="navbar-nav mr-auto">
  <li repeat.for="row of router.navigation" class="nav-item ${row.isActive ? 'active' : ''}">          
    <a href.bind="row.href" class="nav-link">${row.title}</a>
  </li>
</ul>

Quite often, maintaining a single state is less than ideal - the state of “being authenticated” is a likely candidate to also be supported.

Can that be supported just by using binding and without creating too convoluted HTML?

1 Like

I am using Auth0 for authentication and have the navbar changing state based on being authenticated or not, as well as checking if the user is the proper role.

Disclaimer: This was tossed together quickly and could probably be done better, but it is working for me at this point and is just one way of doing it.

In app.ts I have setup the router map with two additional properties called auth and roles.
auth is a boolean with true meaning the user must be authenticated to see it.
roles is a string[] containing the user required roles to view the nav item

Here are a couple of examples of what I am using there

  {
        route: ['edit-member/:id'],
        name: 'edit-member',
        moduleId: PLATFORM.moduleName('./pages/edit-member'),
        nav: false,
        title: 'Edit Member',
        auth: true,
      },
      {
        route: ['manage-members/:id?'],
        name: 'manage-members',
        moduleId: PLATFORM.moduleName('./pages/manage-members'),
        nav: true,
        title: 'Members',
        auth: true,
        roles: ["instructor", "manager", "admin"],
        href: 'manage-members/0'
      },

In my auth service I send events on login successful, and logoff that will change the navbar items displayed.

Here is the code for nav-bar.ts

import { inject } from 'aurelia-framework';
import * as log from 'toastr';
import { Router } from 'aurelia-router';
import { AuthService } from 'services/auth-service';
import { EventAggregator } from 'aurelia-event-aggregator';

@inject(Router, EventAggregator, BindingSignaler, AuthService)
export class NavBar
{
    subLoginComplete: any;
    subLogoffComplete: any;
    routes = [];
    isAuthenticated: boolean = false;

    constructor(public router: Router, private ea: EventAggregator, private authService: AuthService)
    {
        console.log("NavBar Constructor");

        this.subLoginComplete = this.ea.subscribe("LoginComplete", () =>
        {
            console.log("NavBar LoginComplete signal");
            this.isAuthenticated = this.authService.isAuthenticated();
            this.changeRoutes();
        });
        
        this.subLogoffComplete = this.ea.subscribe("LogoffComplete", () =>
        {
            console.log("NavBar LogoffComplete signal");
            this.isAuthenticated = this.authService.isAuthenticated();
            this.changeRoutes();
        });
    }

    attached()
    {
        this.routes = this.router.navigation;
        this.changeRoutes();
    }

    dispose()
    {
        this.subLoginComplete.dispose();
        this.subLogoffComplete.dispose();
    }

    private changeRoutes()
    {
        this.routes = [];
        this.routes = this.router.navigation;
    }

    showNav(navItem)
    {
        console.log("showNav: " + navItem.config.name);

        if (!navItem.config.roles)
        {
            console.log("no roles define, return true");
            return true;
        }

        let enable: boolean = this.authService.isAuthorized(navItem.config.roles);
        console.log(navItem.config.name + " " + (enable ? "enabled" : "disabled"));

        return enable;
    }

    login()
    {
        this.authService.login();
    }

    logout()
    {
        this.authService.logout();
    }
}

Next is the html for the navbar, and the magic happens in the authFilter value convertor

<template>
  <require from="../value-converters/auth-filter"></require>
  
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <a class="navbar-brand" href="#">The Flight Hangar</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
      
        <div class="navbar-collapse collapse" id="navbarSupportedContent">
            <ul class="navbar-nav mr-auto">
                <li repeat.for="row of routes | authFilter" class="nav-item ${row.isActive ? 'active' : ''}">
                    <a class="nav-link" href.bind="row.href">${row.title}</a>
                </li>
            </ul>
            <ul class="navbar-nav pull-right">
                <li class="nav-item" click.delegate="login()" show.bind="!isAuthenticated">
                    <a class="nav-link">Log In</a>
                </li>
                <li class="nav-item"  href="#" click.delegate="logout()" show.bind="isAuthenticated">
                    <a class="nav-link" href="#">${authService.currentUser.name} - Log Out</a>
                </li>
            </ul>
        </div>
      </nav>
      
</template>

Finally here is the authfilter code that determines if the item should be displayed or not.

import { Router } from "aurelia-router";
import { AuthService } from "services/auth-service";
import { inject } from "aurelia-framework";

@inject(Router, AuthService)
export class AuthFilterValueConverter
{
  constructor(public router: Router, private authService: AuthService)
  {
    console.log("NavBar Constructor");
  }

  showNav(navItem) : boolean
  {
    console.log("showNav: " + navItem.config.name);

    if (!navItem.config.roles)
    {
      console.log("no roles define, return true");
      return true;
    }

    let enable: boolean = this.authService.isAuthorized(navItem.config.roles);
    console.log(navItem.config.name + " " + (enable ? "enabled" : "disabled"));

    return enable;
  }

  toView(routes)
  {
    return routes.filter(r => this.showNav(r));
  }
}

Hopefully this might get you started.

1 Like

Thank you @airboss001 for sharing all these details. As you will soon be able to verify, I took very similar paths, so I feel good finding a person with the similar approach to authentication. At the moment, I am creating a whole “Quick Tutorials” series for Auth0, that will augment the “set of a single sample” 01-login. After that gets done I will switch focus on Aurelia Community , with a lot “richer” and more involved samples.