Simple menu with ui-router states

Home / Simple menu with ui-router states

Previously in my discussion of how I like to use the ui-router and states for basic layout functionality, I touched on the basic tenants of what I see as prominent in many SPA applications. In this discussion, I’d like to dig a little deeper and illustrate a simplified implementation of a menu navigation system.

In developing a menu navigation system, I initially consider how flexible I need the navigation to be. If users can change states with hopeless abandon, then define your states and use ui-sref and nothing more. If we need more functionality, we need a more robust solution.


With more real-world applications, though, we do want a robust solution. We want to check dirty states before navigation, we want want to perform validation, and, conditionally, determine whether or not the user can even leave the current state. Angular facilitates these things effectively. This post is going to lay the ground-work so that, while we won’t cover all of these use cases, we’ll develop a working code base that will be available for future discussion, implementation, and expansion.

Let’s start. Since we’re using ui-router and its state engine, we need to define our application states. For me, I start with a base abstract state and then I’m going to create (3) child states. This will be the foundation for our navigation service, and subsequently, our menu navigation.

ui-router makes defining nested/hierarchical states straight forward. I start with a top-level abstract, and nest my other states within that top level like so:

myApp.config(['$modalProvider', '$locationProvider', '$stateProvider', '$urlRouterProvider',
	function ($modalProvider, $locationProvider, $stateProvider, $urlRouterProvider) {
	    $modalProvider.options = { dialogFade: true, backdrop: 'static', keyboard: false };
	    $locationProvider.html5Mode(false);

	    $urlRouterProvider
			.when('/', '/state1')
			.otherwise("/state1");

	    $stateProvider
			.state('app', {
			    abstract: true,
			    url: '/',
			    views: {
			        'main': { template: '<div ui-view>/div>' }
			    }
			})
			.state('app.state1', {
			    url: 'state1',
			    templateUrl: 'state1.html',
			    controller: function () { },
			    reloadOnSearch: false
			})
			.state('app.state2', {
			    url: 'state2',
			    templateUrl: 'state2.html',
			    controller: function () { },
			    reloadOnSearch: false
			})
			.state('app.state3', {
			    url: 'state3',
			    templateUrl: 'state3.html',
			    controller: function () { },
			    reloadOnSearch: false
			})
	}]);

This setup provides us with our “default” state utilizing $urlRouterProvider, our three states (which we’ll use for our menu items), and defines our templates/controllers for the states. So far, so good.

From here, we’ll create our menuService which will tie our states into menu items, provide rendering objects/helpers for our menu, and event handlers for detecting when state changes.

The first iteration of our menu/navigation service defines an array of “menu items” that will be our helper list to render the menu. Each object contains information such as whether it’s active, its corresponding state, and a name for display. We’ll also utilize ui-router’s state providers’ events such as $stateChangeStart and $stateChangeSuccess so that we can set the proper menu items active and such. In a future iteration, we’ll do the cooler stuff, but we have to start somewhere.

Here’s our menuService:

var menuService = function ($rootScope) {
    var menuItems = [
            { name: 'state1', heading: "State1", route: "app.state1", active: false },
            { name: 'state2', heading: "State2", route: "app.state2", active: false },
            { name: 'state3', heading: "State3", route: "app.state3", active: false }
    ];
    var
        currentMenuItem,
        resetMenuItem = function (menuItem) {
            menuItem.active = false;
        },
        resetMenuItems = function () {
            for (var i = 0; i < menuItems.length; i++) {
                resetMenuItem(menuItems[i]);
            }
        },
        findMenuItem = function (routeName) {
            var criteriaFunction = function (c) {
                return c.route === routeName || routeName.indexOf(c.route) != -1;
            };
            return menuItems.filter(criteriaFunction)[0];
        };

    $rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
        currentMenuItem = findMenuItem(toState.name, toParams);
    });

    $rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
        if (currentMenuItem) {
            currentMenuItem.active = true;
            prevMenuItem = findMenuItem(fromState.name);
            if (prevMenuItem && prevMenuItem.name !== currentMenuItem.name) {
                prevMenuItem.active = false;
            }
        } else {
            for (var i = 0; i < currentMenuItem.length; i++) {
                currentMenuItem[i].active = false;
            }
        }
    });

    return {
        menuItems: menuItems,
        currentMenuItem: currentMenuItem
    };
};

You can see that we’re utilizing $stateChangeStart and $stateChangeSuccess $on event. Their function is pretty self explanatory. ui-router will fire these events as appropriate and when we detect them, we’ll set our ‘currentMenuItem’ and once the change of state completes, we’re just going to set the appropriate “active” flags on the menu items. We’ll use this active flag for setting styles in our menu.

Without a controller, our service can’t do much. We’ll create a menuController, but its implementation will not provide significant functionality, so you can check its source at the end of the article in the working example.

Let’s take a look, though, at some of our HTML. Our views are pretty simple. Since we defined our abstract base state, and defined a “view” object within it named “main,” ui-router will look for the ng-view directing called “main” in our DOM. ui-router utilizes this place holder to let us inject a (yet another) placeholder where all of the abstract view’s children will be rendered (phew, that’s a mouthful!). The concept is pretty succinct and works well, imho.

This is how it looks in HTML. We have the ui-view called main and then an ng-include where I rendered our menu (finally):

<div ng-include="'menu.html'"></div>
<div ui-view="main"></div>

<script type="text/ng-template" id="menu.html">
    <nav class="navbar navbar-default" ng-controller="menuCtrl as mn">
      <div class="container-fluid">
        <div class="navbar-header">
            <span class="navbar-brand">Our Menu</span>
        </div>
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
          <ul class="nav navbar-nav">
              <li ng-repeat="m in mn.menuItems track by $index" ng-class="{'active': m && m.active}">
                  <a ng-href="{{ mn.getRouteSref(m) }}" ng-bind="m.heading" />
                </li>
          </ul>
        </div>
      </div>
    </nav>
</script>

You can see that our menu is simply a bootsrap menu that has an ng-repeat to render all of the menuItems that our controller gets from the menuService. We use ng-class to set (or remove) the active CSS class on the right items. You’ll notice the ng-href tag that calling a method in our controller to get the appropriate hyperlink for the state indicated in the repeateor. Usually, I would use ui-router’s provided ui-sref directive that does this, but there is some whacky bug in jsfiddle which my implementation works around.

Imagine now that if we wanted to add more states. All we have to do is add more state objects and the HTML/services we’ve defined take care of the plumbing for us.

At this point, we have most of the parts that we need to get create a menu system that with links that direct the user to our states and will highlight, in bootstrap fashion, the active state in our nav menu.

Below is a fully working implementation. Next time, we’ll take the menuService a bit further and turn it into a full blown navigationService that will allow controllers to provide “knowledge” of their state, call backs, and such which will allow us to provide some nice, reusable functionality as we strive for a robust, flexible, navigation solution.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.