Highlighting the Active Menu Item In AngularJS

Friday, June 7, 2013

Most applications feature menu items that will need to appear selected at the appropriate time.

How to do this with Angular?

There are many different approaches, but ideally we’ll do this with a directive, since directives are responsible for DOM manipulation, and “selecting” a menu item will require some manipulation of the classes on an element. The goal would be to make it as easy as possible from a view:

<nav>
    <ul data-active-menu="selected">
        <li><a href="#/details?q=foo">Details</a></li>
        <li><a href="#/test">Summary</a></li>
        ...
    </ul>
</nav>

In the above code, the data-active-menu attribute should allow a custom directive to jump in and manipulate the styles of the elements inside by adding “selected” as a class to the active link, and removing the class from all other links.

The directive code itself would look like this:

(function () {
    
    var makeWatcher = function(location) {
        return function() {
            return location.url();
        };
    };

    var makeLinkUpdater = function(links, className) {
        return function (value) {
            angular.forEach(links, function(link) {
                link = angular.element(link);
                if (/\#(\/[^\/]+)/.exec(link.attr("href"))[1] == value) {
                    link.addClass(className);
                } else {
                    link.removeClass(className);
                }
            });
        };
    };

    var activeMenu = function($location) {

        var link = function(scope, element, attrs) {
            var links = element.find("a");
            var className = attrs.activeMenu;
            scope.$watch(makeWatcher($location),
                         makeLinkUpdater(links, className));
        };

        return {
            link:link
        };
    };
    activeMenu.$injector = ["$location"];
    
    angular.module("testApp")
           .directive("activeMenu", activeMenu);
}());

By taking a dependency on the $location service in AngularJS, the directive can both watch for when the location changes, and compare the current location to the href in the menu items. Matching hrefs get the class specified in the data-menu-active attribute. Easy, reusable, and keeps more boilerplate code out of the controllers.


Comments
gravatar Bas Slagter Friday, June 7, 2013
I did it by creating a main menu directive and a global list of my app's pages (could also be some kind of service of course). Below the simplified version: return myApp.directive('mainMenu', function () { return { restrict: 'E', templateUrl: 'partials/directives/main-menu.html', replace: true, controller: function ($rootScope, $scope, $location, $routeParams) { $scope.items = _.map(myApp.pages, function (page, i) { return { name: page, path: '#/' + page, active: !i // Activate the first on default }; }); $rootScope.$on('$routeChangeSuccess', function () { _($scope.items).each(function (item) { item.active = (item.path == '#' + $location.$$path); }); }); } } });
gravatar Scott Allen Friday, June 7, 2013
@Bas: Very cool! Sorry about the code formatting in comments.
Comments are closed.

My Pluralsight Courses

K.Scott Allen OdeToCode by K. Scott Allen
What JavaScript Developers Should Know About ECMAScript 2015
The Podcast!