AngularJS Click to Close Menu

May 22, 2015

Recently, at work, I came across (or was told to fix) a need to give a more natural feel to the UX on our web app. We have various menus throughout the app that can be toggled by clicking/touching an element, but the only way to close them is to click/touch the same element again. With mobile UX paradigms making their way into every aspect of our lives, I had to rethink the original approach on how to show or hide content when the user’s interactions are required. I’m used to swipes and clicking off of elements to close them, so let’s bring them to our web experience.

AngularJS has a great set of directives, including ng-show, which I use extensively to display or hide content based on user interaction or the model.

The app is becoming more complex and allows for more interaction that originally designed, so it was time to sit down and fix the wonky interaction!

Here’s how I solved the problem.

  1. Create a directive to throw on an element that will be interacted with
  2. (Optional) Add a class to child elements where you want to prevent the default close behavior. For this, you’ll need to modify the directive to check for the existence of the child class.

Directive

'use strict';
angular.module('app')
    .directive('closeOnClick', function () {
        return {
            scope: {
                watchValue: '='
            },
            restrict: 'EA',
            link: function (scope, element) {
                element.bind('click', function (e) {
                    scope.watchValue = !scope.watchValue;
                    // prevents a double click behavior                    
                    e.stopPropagation();
                    // let Angular know we're doing our thing
                    scope.$apply()
                });
            },
            controller: function ($scope, $document) {
                $document.on('click', function (e) {
                    if ($scope.watchValue) {
                        $scope.watchValue = false;
                        $scope.$apply();
                    }
                });
            }
        }
});

What is happening is an event handler is bound to the element on creation (during the linking phase). This is what listens to our click and inverts the the watchValue that we defined. The controller function takes care of listening for clicks on the document as a whole and if the menu or object is open (or truthy), then, let’s go ahead and close/hide the element.

Usage in the view

<div close-on-click watch-value="showStuff">
    <div ng-show="showStuff">
        <h1>Cool hidden stuff</h1>
    </div>
</div>

That’s it! Now, we have an improved user experience with Angular’s most powerful component, directives!