title: How to handle document click and notify other controllers using AngularJS?
body: <p>I have created a horizontal drop down menu using AngularJS.</p>
  
  <p>The menu section is managed by an angular controller called menuController. Standard menu behavior is implemented, so that on hover main menu item gets highlighted unless it is disabled. On clicking the main menu item, the sub menu toggles. If Sub menu is in a open state, I want it to go away when user clicks anywhere else on the document. I tried to create a directive to listen for document click event but not sure on how to notify menu-controller about it. How should I implement this scenario in a AngularJS way?</p>
  
  <p>Partially working <strong><a href="http://plnkr.co/edit/3Awc2l?p=preview" rel="nofollow noreferrer" title="Original Plunk">Original Plunk</a></strong> without document click handling mechanism.</p>
  
  <p><strong>UPDATE:</strong></p>
  
  <p>Based on answered suggestion, I went with Brodcast approach and updated the script to reflect my latest changes. It is working as per my expectation. I made the globalController $broadcast a message and menuController subscribe to that message.</p>
  
  <p><strong>UPDATE 2:</strong> Modified code to inject global events definition data.</p>
  
  <pre><code>var eventDefs = (function() {
    return {
      common_changenotification_on_document_click: 'common.changenotification.on.document.click'
    };
  }());
  
  var changeNotificationApp = angular.module('changeNotificationApp', []);
  
  changeNotificationApp.value('appEvents', eventDefs);
  
  changeNotificationApp.directive("onGlobalClick", ['$document', '$parse',
    function($document, $parse) {
      return {
        restrict: 'A',
        link: function($scope, $element, $attributes) {
          var scopeExpression = $attributes.onGlobalClick;
  
          var invoker = $parse(scopeExpression);
  
          $document.on("click",
            function(event) {
              $scope.$apply(function() {
                invoker($scope, {
                  $event: event
                });
              });
            }
          );
        }
      };
    }
  ]);
  
  changeNotificationApp.controller("globalController", ['$scope', 'appEvents',
    function($scope, appEvents) {
      $scope.handleClick = function(event) {
        $scope.$broadcast(appEvents.common_changenotification_on_document_click, {
          target: event.target
        });
      };
    }
  ]);
  
  //menu-controller.js
  changeNotificationApp.controller('menuController', ['$scope', '$window', 'appEvents',
    function($scope, $window, appEvents) {
  
      $scope.IsLocalMenuClicked = false;
  
      $scope.menu = [{
        Name: "INTEGRATION",
        Tag: "integration",
        IsDisabled: false,
        IsSelected: false,
        SubMenu: [{
          Name: "SRC Messages",
          Tag: "ncs-notifications",
          IsDisabled: false,
          AspNetMvcController: "SearchSRCMessages"
        }, {
          Name: "Target Messages",
          Tag: "advisor-notifications",
          IsDisabled: false,
          AspNetMvcController: "SearchTaregtMessages"
        }]
      }, {
        Name: "AUDITING",
        Tag: "auditing",
        IsDisabled: true,
        IsSelected: false,
        SubMenu: []
      }];
  
      $scope.appInfo = {
        Version: "1.0.0.0",
        User: "VB",
        Server: "azzcvy0623401v",
        IsSelected: false
      };
  
      var resetMenu = function() {
        angular.forEach($scope.menu, function(item) {
          item.IsSelected = false;
        });
        $scope.appInfo.IsSelected = false;
      };
  
      $scope.toggleDropDownMenu = function(menuItem) {
        var currentDropDownState = menuItem.IsSelected;
        resetMenu($scope.menu, $scope.appInfo);
        menuItem.IsSelected = !currentDropDownState;
        $scope.IsLocalMenuClicked = true;
      };
  
      $scope.loadPage = function(menuItem) {
        if (menuItem.AspNetMvcController)
          $window.location.href = menuItem.AspNetMvcController;
      };
  
      $scope.$on(appEvents.common_changenotification_on_document_click,
        function(event, data) {
          if (!$scope.IsLocalMenuClicked)
            resetMenu($scope.menu, $scope.appInfo);
          $scope.IsLocalMenuClicked = false;
        });
    }
  ]);
  </code></pre>
  
  <p><strong>UPDATE 3:</strong> Modified code in previous implementation to fix a bug where document click fires multiple times. Almost similar approach, but this time, if any one clicks again anywhere on the menu, the click is ignored. Please refer to the <strong><a href="http://plnkr.co/edit/d3RASE?p=preview" rel="nofollow noreferrer" title="Plunk">New Working Plunk</a></strong> for full code example</p>
  
  <pre><code>    changeNotificationApp.directive("onGlobalClick", ['$document', '$parse',
      function ($document, $parse) {
          return {
              restrict: 'A',
              link: function ($scope, $element, $attributes) {
                  var scopeExpression = $attributes.onGlobalClick;
  
                  var invoker = $parse(scopeExpression);
  
                  $document.on("click",
                      function (event) {
                         var isClickedElementIsChildOfThisElement = $element.find(event.target).length &gt; 0;
                              if (isClickedElementIsChildOfThisElement) return;
                          $scope.$apply(function () {
                              invoker($scope, {
                                  $event: event
                              });
                          });
                      }
                  );
              }
          };
      }
  ]);
  </code></pre>
  
  <p><strong>UPDATE 4:</strong> Implemented another alternate option. Please refer to the <strong><a href="http://plnkr.co/edit/fEdOH2?p=preview" rel="nofollow noreferrer" title="Option 2 Plunk">Option 2 Plunk</a></strong> for full code example</p>
  
  <pre><code>    var eventDefs = (function () {
      return {
          on_click_anywhere: 'common.changenotification.on.document.click'
      };
  }());
  
  var changeNotificationApp = angular.module('changeNotificationApp', []);
  
  changeNotificationApp.value('appEvents', eventDefs);
  
  changeNotificationApp.directive("onClickAnywhere", ['$window', 'appEvents',
    function($window, appEvents) {
      return {
        link: function($scope, $element) {
          angular.element($window).on('click', function(e) {
            // Namespacing events with name of directive + event to avoid collisions
            $scope.$broadcast(appEvents.on_click_anywhere, e.target);
          });
        }
      };
    }
  ]);
  
  //menu-controller.js
  changeNotificationApp.controller('menuController', ['$scope', '$window', 'appEvents', '$element',
      function ($scope, $window, appEvents, $element) {
  
          $scope.menu = [
              {
                  Name: "INTEGRATION",
                  Tag: "integration",
                  IsDisabled: false,
                  IsSelected: false,
                  SubMenu: [
                      {
                          Name: "SRC Messages",
                          Tag: "ncs-notifications",
                          IsDisabled: false,
                          AspNetMvcController: "SearchSRCMessages"
                      },
                      {
                          Name: "Target Messages",
                          Tag: "advisor-notifications",
                          IsDisabled: false,
                          AspNetMvcController: "SearchTaregtMessages"
                      }
                  ]
              },
              {
                  Name: "AUDITING",
                  Tag: "auditing",
                  IsDisabled: true,
                  IsSelected: false,
                  SubMenu: []
              }
          ];
  
          $scope.appInfo = {
              Version: "1.0.0.0",
              User: "VB",
              Server: "azzcvy0623401v",
              IsSelected: false
          };
  
          var resetMenu = function () {
              angular.forEach($scope.menu, function (item) {
                  item.IsSelected = false;
              });
              $scope.appInfo.IsSelected = false;
          };
  
          $scope.toggleDropDownMenu = function (menuItem) {
              var currentDropDownState = menuItem.IsSelected;
              resetMenu($scope.menu, $scope.appInfo);
              menuItem.IsSelected = !currentDropDownState;
          };
  
          $scope.loadPage = function (menuItem) {
              if (menuItem.AspNetMvcController)
                  $window.location.href = menuItem.AspNetMvcController;
          };
  
          $scope.$on(appEvents.on_click_anywhere, function(event, targetElement) {
            var isClickedElementIsChildOfThisElement = $element.find(targetElement).length &gt; 0;
            if (isClickedElementIsChildOfThisElement) return;
  
            $scope.$apply(function(){
              resetMenu($scope.menu, $scope.appInfo);
            });
          });
      }
  ]);
  </code></pre>