Codementor Events

AngularJS Tutorial Series: Part 3 – How to Make a Sliding Menu with Directives

Published Dec 04, 2014Last updated Aug 14, 2017

This intermediate AngularJS mini tutorial is part 3 of a 5 part series with each teaching you how to build something you can use on your web page. This tutorial will teach you how to make a sliding menu using AngularJS directives among other tools.

Other Posts in the Series:

Angular JS Sliding Menu

I’d like to talk about how to create a sliding menu control using Angular JS and LESS CSS. The basic idea is that I want a menu to slide in from either the left or the right side of the screen based on some external controller action.

Angularjs sliding menu

If you’re the kind of person who wants to see the big picture instead of having a step by step tutorial laid out for you, I’ve put together a demo page, which you can see here. The menu itself will be an Angular JS directive which takes just two arguments: visible and alignment. Visible indicates which controller variable should be responsible for showing the menu, and the alignment variable determines whether or not the menu slides in from the left side of the screen or the right side of the screen.

This article will be broken down into three separate sections: the HTML, the JavaScript, and the CSS. The HTML shows how I’ve got the menu directive laid out, and also provides example usage. The JavaScript is the meat of the article, and will explain how to create the sliding menu directive, as well as how to tie the Angular JS controller into the directive itself. The CSS is a little complicated, as I’m adding fancy animations to do the actual sliding, but outside of that, it’s just plain old styling.

HTML

Sample Usage

Use the following to add the sliding menu directive to your page, where “visible” and “alignment” are variables on your controller.

<menu visible="visible" alignment="left">
    <menu-item hash="first-page">First Page></menu-item>
    <menu-item hash="second-page">Second Page></menu-item>
    <menu-item hash="third-page">Third Page></menu-item>
</menu>

Directive

Here’s the HTML for the directive template. It’s relatively straightforward, but I’d still recommend you put this in a separate file for ease of use. The only interesting thing going on here is the use of the ng-class directive, which indicates that a CSS class be added to the indicated element based on the truthiness of the specified variable. In this case, if visible is truthy (meaning not null or undefined), the show class will be added to the parent div. I’m also setting the alignment class here, too, with left assigning the left class, and right the right, respectively. The ng-transclude directive is used to render child elements into the directive. For example, the sample usage above denotes three menu-items; those three nodes would be placed inside the rendered HTML for the menu, as desecribed below.

<div ng-class='{ show: visible, left: alignment === \"left\", right: alignment === \"right\" }' ng-transclude></div>

JavaScript

The JavaScript is broken down into a couple of different sections. First, there’s the initialization of the Angular JS application. Second, we’ll take a look at the controller that initializes the directive. And finally, we’ll take a look at the two directives used to render the menu: the menu itself, and the nested menu items.

Application

Here, we’re initializing the Angular JS application, and then hooking up some events that we can use to hide the menus once they’re revealed. When the user hits escape or clicks anywhere on the document, the menus should hide. Angular JS has event emitters built into its scope objects, so we’re making use of those on the $rootScope variable, which is the overarching scope that is the eventual parent of all of the other scopes in your application.

Note: You’ll have to stop the propagation of the event used to trigger the showing of the menus, because if you don’t, this document-wide click handler will immediately hide the menu after it’s shown. Take a look at the test page for an example of how to do this.

var app = angular.module("demo", []);

app.run(function($rootScope) {
    document.addEventListener("keyup", function(e) {
        if (e.keyCode === 27)
            $rootScope.$broadcast("escapePressed", e.target);
    });

    document.addEventListener("click", function(e) {
        $rootScope.$broadcast("documentClicked", e.target);
    });
});

Controller

Here’s an example as to how your controller would manipulate the menus so as to show and hide them. The showLeft and showRight functions would show the left and right menus, respectively, and we’re tying into the aforementioned event emiiter on the $rootScope variable via the $on methods to close the menus when appropriate.

app.controller("modalDemo", function($scope, $rootScope) {
    $scope.leftVisible = false;
    $scope.rightVisible = false;

    $scope.close = function() {
        $scope.leftVisible = false;
        $scope.rightVisible = false;
    };

    $scope.showLeft = function(e) {
        $scope.leftVisible = true;
        e.stopPropagation();
    };

    $scope.showRight = function(e) {
        $scope.rightVisible = true;
        e.stopPropagation();
    }

    $rootScope.$on("documentClicked", _close);
    $rootScope.$on("escapePressed", _close);

    function _close() {
        $scope.$apply(function() {
            $scope.close(); 
        });
    }
});

Directives

Here’s the code to control the two directives in the menu. Both are restricted to elements (via the restrict: "E" option), and we’re transcluding the inner content of both into each directive’s content. This means that the inner contents of the menu directive in the example above get written into the menu’s immediate child div. The inner content of each menu-item tag (namely the text we want to show in the menu items) get rendered within each menu item, too. Angular JS knows where to put the transcluded contents via the ng-transclude directive, which you see in both templates below. For the menu directive, we’ve created an isolated scope containing the visible two-way binding and the alignment one-way binding. This allows us to read the visible variable’s value and the alignment’s string value. We’re doing similarly with the hash variable in the menu item’s isolated scope. Finally, the menu item has a method added to its scope via the link option’s function, navigate, which is fired when the user clicks on the menu item, as seen by the ng-click directive.

app.directive("menu", function() {
    return {
        restrict: "E",
        template: "<div ng-class='{ show: visible, left: alignment === \"left\", right: alignment === \"right\" }' ng-transclude></div>",
        transclude: true,
        scope: {
            visible: "=",
            alignment: "@"
        }
    };
});

app.directive("menuItem", function() {
     return {
         restrict: "E",
         template: "<div ng-click='navigate()' ng-transclude></div>",
         transclude: true,
         scope: {
             hash: "@"
         },
         link: function($scope) {
             $scope.navigate = function() {
                 window.location.hash = $scope.hash;
             }
         }
     }
});

CSS

Finally, here’s the CSS. The first three lines describe some LESS CSS mixins I’ve defined to help ease the vendor prefix nightmare. The first is for defining transitions (or animations); the second is for CSS transformations; and the third is to set the border-box value for box-sizing. Below that is the CSS for the menu, and within that, each menu item. I’m making liberal use of the & LESS CSS command, which allows me to specify styles for various conditions, which in this case are additional classes, like left, show, etc. Those are almost exclusively used to define positioning of the menus depending on the specified alignment.

.transition (@value1,@value2:X,...) { @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`; -webkit-transition: @value; -moz-transition: @value; -ms-transition: @value; -o-transition: @value; transition: @value; }
.transform (@value1,@value2:X,...) { @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`; transform:@value; -ms-transform:@value; -webkit-transform:@value; -o-transform:@value; -moz-transform:@value; }
.border-box { box-sizing:border-box; -moz-box-sizing:border-box; }

menu { display:block;
    @menu-width:250px;
    >div { position:absolute; z-index:2; top:0;  width:@menu-width; height:100%; .border-box; .transition(-webkit-transform ease 250ms); .transition(transform ease 250ms);
        &.left { background:#273D7A; left:@menu-width*-1; }
        &.show.left { .transform(translate3d(@menu-width, 0, 0)); }

        &.right { background:#6B1919; right:@menu-width*-1; }
        &.show.right { .transform(translate3d(@menu-width*-1, 0, 0)); }

        >menu-item { display:block;
            >div { float:left; width:100%; margin:0; padding:10px 15px; border-bottom:solid 1px #555; cursor:pointer; .border-box; color:#B0B0B0;
                &:hover { color:#F0F0F0; }
                >span { float:left; color:inherit; }
            }
        }
    }
}

Conclusion

That’s it! I hope this has taught you something as you’ve read through this. If you’d like to see these menus in action, you can check out the demo page, which adds both a left and a right menu to the page. Thanks for reading!

Discover and read more posts from Chris Harrington
get started
post commentsBe the first to share your opinion
Antony
7 years ago

how to use push the content in leftside…

Jason Neal
8 years ago

So this code only works with a single controller. If I have multiple controllers, I have to add this code to every single one. Doesn’t really seem like the angular way.

Alex peguero-cruz
7 years ago

use scope: {} in your directive so that it’s isolated to that particular module

Show more replies