An Angular 'Any Other Click' Directive

I recently implemented a very plain popup menu in Angular. Showing the menu was simple, as you would expect, but dismissing the menu threw me for a bit of a loop. How could I have my menu respond to a click event outside of the element hosting its directive? After a bit of thought, I wrote the following ‘any other click’ directive. Basically, the directive registers a click event on the document element. This click event checks to see if it was generated from the element that registered the directive. If not, it executes the code specified in the directive attribute. jQuery is required since angular.element.find() is limited to lookups by tag name.

Directive Source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(function () {
    var module = angular.module('anyOtherClick', []);

    var directiveName = "anyOtherClick";

    module.directive(directiveName, ['$document', "$parse", function ($document, $parse) {
        return {
            restrict: 'A',
            link:  function (scope, element, attr, controller) {
                var anyOtherClickFunction = $parse(attr[directiveName]);
                var documentClickHandler = function (event) {
                    var eventOutsideTarget = (element[0] !== event.target) && (0 === element.find(event.target).length);
                    if (eventOutsideTarget) {
                        scope.$apply(function () {
                            anyOtherClickFunction(scope, {});
                        });
                    }
                };

                $document.on("click", documentClickHandler);
                scope.$on("$destroy", function () {
                    $document.off("click", documentClickHandler);
                });
            },
        };
    }]);
})();

Plunker Demo

Items of Interest

  • Angular exposes an $event object during ng-click. We use $event.stopPropagation() in the ng-click expression that shows the popup to prevent the popup’s any-other-click expression from firing and immediately hiding the popup.
  • As noted above, jQuery is required since angular.element.find() is limited to lookups by tag name. Alternative pure-Angular suggestions would be greatly appreciated.

Disclaimer

The code above is working for me in production. However, I only use this directive a few times on my pages. As with most things in Angular, you’d need to re-evaluate this directive’s performance if it is used in a large ng-repeat or other looping constructs.

Update - 2015.01.07

I have updated the directive to avoid creating an isolate scope based on the ng-click source. This lets the directive play nice with other angular directives.


comments powered by Disqus