Listen for multiple events on a $scope

36,240

Solution 1

Yes. Like this:

app.directive('multipleSadness', function() {
    return {
        restrict: 'C',
        link: function(scope, elem, attrs) {

            function sameFunction(eventId) {
                console.log('Event: ' + eventId + '. The Ferrari is to a Mini what AngularJS is to ... other JavaScript frameworks.');
            }

            scope.$on('event:auth-loginRequired', function() {sameFunction('auth-loginRequired');});
            scope.$on('event:auth-loginConfirmed', function () {sameFunction('auth-loginConfirmed');});
        }
    };
});

But just because you can, doesn't mean you should :). If the events are continue to propagate up to another listener and they are handled differently there, then maybe there is a case to do this. If this is going to be the only listener than you should just emit (or broadcast) the same event.

Solution 2

The other answers (Anders Ekdahl) are 100% correct... pick one of those... BUT...

Barring that, you could always roll your own:

// a hack to extend the $rootScope 
app.run(function($rootScope) {
   $rootScope.$onMany = function(events, fn) {
      for(var i = 0; i < events.length; i++) {
         this.$on(events[i], fn);
      }
   }
});

app.directive('multipleSadness', function() {
    return {
        restrict: 'C',
        link: function(scope, elem, attrs) {
            scope.$onMany(['event:auth-loginRequired', 'event:auth-loginSuccessful'], function() {
                console.log('The Ferrari is to a Mini what AngularJS is to ... other JavaScript frameworks');
            });
        }
    };
});

I suppose if you really wanted to do the .split(',') you could, but that's an implementation detail.

Solution 3

AngularJS does not support multiple event binding but you can do something like this:

var handler = function () { ... }
angular.forEach("event:auth-loginRequired event:auth-loginConfirmed".split(" "), function (event) {
    scope.$on(event, handler);
});

Solution 4

I don't think that's possible, since the event might send data to the callback, and if you listen to multiple events you wouldn't know which data came from which event.

I would have done something like this:

function listener() {
    console.log('event fired');
}
scope.$on('event:auth-loginRequired', listener);
scope.$on('event:auth-loginConfirmed', listener);

Solution 5

Also like with $rootScope.$onMany (solution from @ben-lesh) it's possible to extend $on method:

var $onOrigin = $rootScope.$on;

$rootScope.$on = function(names, listener) {
  var self = this;

  if (!angular.isArray(names)) {
    names = [names];
  }

  names.forEach(function(name) {
    $onOrigin.call(self, name, listener);
  });

};

took from here.

Share:
36,240
Michael Robinson
Author by

Michael Robinson

"The best writing is rewriting." E. B. White

Updated on June 08, 2020

Comments

  • Michael Robinson
    Michael Robinson about 4 years

    I have a directive that binds some functions to the local scope with $scope.$on.

    Is it possible to bind the same function to multiple events in one call?

    Ideally I'd be able to do something like this:

    app.directive('multipleSadness', function() {
        return {
            restrict: 'C',
            link: function(scope, elem, attrs) {
                scope.$on('event:auth-loginRequired event:auth-loginSuccessful', function() {
                    console.log('The Ferrari is to a Mini what AngularJS is to ... other JavaScript frameworks');
                });
            }
        };
    });
    

    But this doesn't work. The same example with the comma-separated event name string replaced with ['event:auth-loginRequired', 'event:auth-loginConfirmed'] doesn't wrk either.

    What does work is this:

    app.directive('multipleSadness', function() {
        return {
            restrict: 'C',
            link: function(scope, elem, attrs) {
    
                scope.$on('event:auth-loginRequired', function() {
                    console.log('The Ferrari is to a Mini what AngularJS is to ... other JavaScript frameworks');
                });
                scope.$on('event:auth-loginConfirmed', function() {
                    console.log('The Ferrari is to a Mini what AngularJS is to ... other JavaScript frameworks');
                });
            }
        };
    });
    

    But this is not ideal.

    Is it possible to bind multiple events to the same function in one go?

  • Ryan O'Neill
    Ryan O'Neill over 11 years
    Is there a reason why you would use "event:auth-loginRequired event:auth-loginConfirmed".split(" ") instead of just ['event:auth-loginRequired', 'event:auth-loginConfirmed']?
  • bmleite
    bmleite over 11 years
    @RyanO'Neill No special reason =) It just seems more neat having a single string than having ['','','',...]. Also OP's example was with a single string separated by commas.
  • Ryan O'Neill
    Ryan O'Neill over 11 years
    Thanks. JavaScript being what it is, just wanted to make sure I wasn't missing a Wat moment that I should know about.
  • Michael Robinson
    Michael Robinson over 11 years
    Thanks for the extra information regarding event propagation & event ID's, knowing this I understand why what I ask isn't natively possible. The events do have different listeners, this one listener needs to perform the same action on multiple events however.
  • Michael Robinson
    Michael Robinson over 11 years
    After consideration I have refactored my code to be more Angular. There are still two events, but as they signify different things (in the refactoring), there is no reason to bind them both to the same function. Thanks for the pointers.
  • testing123
    testing123 over 11 years
    No problem at all. Glad I could help.
  • Michael J. Calkins
    Michael J. Calkins over 10 years
    You should submit this as a PR I could use this. Maybe I'll just take your credit.
  • Verdi Erel Ergün
    Verdi Erel Ergün almost 10 years
    Checking the length on each iteration is very expensive; it's better to assign the length to a variable
  • Ben Lesh
    Ben Lesh almost 10 years
    @VerdiErelErgün "very expensive"? How many times per ms do you expact this to get hit? It's a one-time call to set up some bindings. Micro-optimization wasn't really the point here... But yes, one could for(var i = 0, len = events.length; i < len; i++) { ... } ... or even var i = events.length; while(i--) { ... } if they really cared to.
  • Dmitry Gonchar
    Dmitry Gonchar over 8 years
    @BenLesh Any idea how to make this work with an isolated scope? Or just create a factory method and pass scope to it?
  • cipak
    cipak over 7 years
    you should modify $rootScope.__proto__.$on (or $rootScope.constructor.prototype.$on) if you want child scopes to also inherit the modified function.
  • Roi
    Roi about 7 years
    I wrote this on the fly... but you need to be able to end listeners too... app.run(function($rootScope) { $rootScope.$onMany = function(events, fn) { var listeners = {} for(var i = 0; i < events.length; i++) { listeners[events[i]] = this.$on(events[i], fn); } return listeners; } });