Inside a directive I am trying to get a list of all event names that can potentially be captured by the given scope.
When observing the scope object, I can see that there is a $$listeners
property, which contains a single function, and a $$listenersCount
property, which does in fact seem to contain a list of events which I have defined that are relevant to the given scope.
I am listening to most of these events on child scopes of the one that is displayed, so I'm assuming this is a list off all events "passing through" the given scope, not events which the specific scope is listening to. I'm unsure what the numbers mean, though.
I can't find any documentation on these properties so I am assuming they are an internal thing that shouldn't be used for this specific purpose.
Are there any other ways of retrieving a list such as this or do you think it's safe to use despite the lack of documentation?
Let's first consider using $$listeners. Like @Blackhole quoted in his comment:
To prevent accidental name collisions with your code, Angular prefixes names of public objects with $ and names of private objects with $$. Please do not use the $ or $$ prefix in your code.
So $$listeners is private to angular. It has no documentation and can introduce a breaking change at any moment without notice. Also it's not exactly what you want. By looking at the code of $scope.$on we can see that, like you guessed, $$listenerCount bubbles up the scope to its $parent, all the way to root. The numbers are a count of how many listeners are listening to that one event. You might be able to get away with using $$listeners if you were developing some internal tool to debug events, but using in a production site would not be the wisest thing.
So what would be a documented way to achieve this? Angular provides a decorator method on its provider. While the documentation on this is little, it is quite a powerful tool. It essentially allows interception of any part of angular, and act as a man in the middle (see more info on the Decorator pattern and Monkey patching on Wikipedia). Using these tools we can create a configuration that will capture each instance of $on being called:
.config(function ($provide) {
function wrap(oldFn, wrapFn) {
return function () {
return wrapFn.bind(this, oldFn)
.apply(this, arguments);
}
}
$provide.decorator('$rootScope', function ($delegate) {
var proto = Object.getPrototypeOf($delegate);
proto.$on = wrap(proto.$on, function ($on, name, listener) {
var deregister = $on.call(this, name, listener);
console.log(this, name, listener, deregister);
return deregister;
});
return $delegate;
});
});
Here I have logged each time $on is called. The $scope is the this
variable, name
and listener
are the arguments passed to $on, and deregister
is the return value. Note that his requires ES5's Object.getPrototypeOf, and will add overhead each time $on is called.
From here, getting the potential event listeners is easy. Instead of console.log, you could place them into a map, or hook into specific ones. You could also wrap the listener or delistener and do additional work every time they get called. Here is an example plunker of that.
This is better than using $$listeners, because $$listeners can change in any way, at any time. $on on the other hand, is a published API. It will be far more stable, and it will not change without a "Breaking Change" notification in angular's changelog and is the safer choice, even though it uses more anomalous tools.