Event Bus in AngularJS

4,678 views
Skip to first unread message

Igor Minar

unread,
Jul 22, 2011, 3:50:10 AM7/22/11
to ang...@googlegroups.com
Hi there,

Second action item from our hangout meeting was to find out if there
was interest in the community to spec out and possibly implement event
bus in angular.

To start off, can we gather some use-cases and scenarios in which
having an event bus would solve issues that can't be (easily) dealt
with in angular without an even bus?

/i

rur

unread,
Jul 22, 2011, 5:38:14 AM7/22/11
to angular
Thanks Igor,

While the scope hierarchy is a really useful way of managing state and
triggering logic across tiers, my feeling is that it doesn't provide
the level of abstraction that I sometimes want. For instance say I
want a general panel controller to handle all notifications, but i
dont want it to know the nature or source of the Command. Rather than
having a 'currentCommand' in the root Controller (yuck),
MySettingsPanelController could call:
signals.notify.dispatch( messageObj, okHandler, cancelHandler );

Since I'm coming from AS3 I'm quite attached to the Signals
implementation of the observer pattern, I would like to hear your
thoughts on which implementation you guys prefer.

I've been thinking about the API for the service. What I had in mind
was something like this:
function MyCtrl($signals)
{
var signals = $signals(this);
signals.mySignal.dispatch( param1, param2, param3 );
...
signals.mySignal.add( mySignalHandler );
function mySignalHandler( param1, param2, param3 ){
...
}
}

The idea of passing the scope into the constructor of the bus instance
is so that it can do something like this:

return function ( scope ){
scope.$onDispose( removeAllCallbacks );
....
}

That way the the developer doesn't need to remember to do add teardown
logic to every controller/service that uses the signals object. But
I'm clearly making some assumptions there about the changes to the
Scope API!

The signals service could be configured with new signal types like
so:
$signals.configureSignal( 'mySignal', [ Object, Function,
Function] ).

The first param being the name of the signal created. The second,
optional, param is an array of object types. This could allow for type
checking of parameters when the dispatch method is called on the
signal instance. I'm not sure how best to approach that, however I do
like the idea.

Any thoughts on that?

Thanks,

rur

Vojta Jina

unread,
Jul 24, 2011, 4:45:05 PM7/24/11
to ang...@googlegroups.com
Hey rur,

I like your proposal, especially the idea about auto removing the handlers when scope is destroyed.

I believe, we don't need configureSignal method.... how about this:

signals.add('event_name', handler);
signals.dispatch('event_name', param1, param2, ...);

I would go for Node.js names for these methods:
on() // register handler
emit() // fire the event

V.

rur

unread,
Jul 27, 2011, 4:04:28 PM7/27/11
to angular
Thanks Vojta

I spent a couple of hours putting together a quick and dirty
implementation that has this API. You can see it here:
https://github.com/rur/angular-eventbus

It will work locally, output is in console.

I've gone with the api you suggest because it is a simpler
implementation, and I assume Note.js names would be more familiar to
JS devs.

To get around the '$onDispose' issue for the time being I also wrote a
'scopeWatcher' service which infers that a scope is to be disposed
when it is no longer being eval'ed along with the root scope. It's an
ugly, and probably buggy hack but seems to work well with ng:view
anyway.

My idea of configuring the event/signal was intended to prevent
different events from colliding when they use the same string. If you
call config twice for the same type an error can be thrown. The same
idea for specifying the type of parameters, it provides nice runtime
exceptions!

This is just a loose attempt to get the ball rolling.

Any thoughts guys?

rur

Vojta Jina

unread,
Jul 27, 2011, 5:14:27 PM7/27/11
to ang...@googlegroups.com
Hey rur,

great work, thanks for that !

My notes / feedback:
  • I dont' think we need listenerMap. Think it's ok when user has to define the event name when removing the listener.. The same as browser removeListener()...
  • Missing scope.$onDispose() is fine - I hope we will add this feature in new scope implementation.
  • Where are the unit tests ?
  • Performance - this piece of code is supposed to be run quit often, so I would think about performance - e.g. reducing usage of apply() and closures (for example defining methods directly instead of pointing to them via closure).

I would store all the callbacks in hash object with event names as keys - one storage for whole service. Then each sub-service (related to particular scope) would store extra array of their callbacks, so that when the scope is being removed, it can remove all these callbacks... When you use push on both, then you have the same order of callbacks in both storages, so removing (searching) is gonna be linear...

Regards,
V.

Igor Minar

unread,
Jul 29, 2011, 2:10:44 AM7/29/11
to ang...@googlegroups.com
A small suggestion for
https://github.com/rur/angular-eventbus/blob/master/app/js/services.js#L151
just do "var root = this;" in the service factory service to get hold
of the root scope.

I like your prototype. The idea of figuring out which scopes were dead
is quite nice.

One big concern I have is that in your code the bus is synchronous.
That means that the execution of controller can be affected by some
bus listeners far in the distance. This might or might not be a
problem, but my intuition says that it would be better if the bus was
asynchronous. A naive way of achieving that would be by broadcasting
the event from within the $defer service. A better implementation
would require a hook in the scope life cycle (not a big deal).

Have you thought much about synchronous vs asynchronous behavior of the bus?

/i

> --
> You received this message because you are subscribed to the Google Groups "angular" group.
> To post to this group, send email to ang...@googlegroups.com.
> To unsubscribe from this group, send email to angular+u...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/angular?hl=en.
>
>

Vojta Jina

unread,
Jul 29, 2011, 3:11:06 AM7/29/11
to ang...@googlegroups.com
Why do you want to fire the events async ? It might have better performance - controller doesn't get blocked, but the behavior could be undesired...

Consider this example...

eBus.on('beforeX', function() {
  console.log('before event');
}

eBus.on('afterX', function() {
  console.log('after event');
});

function doX() {
  console.log('doing x');
}

// some job inside controller.... doing x
eBus.emit('beforeX');
doX();
eBus.emit('afterX');

SYNC result:
  1. before event
  2. doing x
  3. after event

ASYNC result:
  1. doing x
  2. before event
  3. after event

Even Node.js events.Emitter fires events synchronously....

Am I missing anything ?

V.


Witold Szczerba

unread,
Jul 29, 2011, 3:29:15 AM7/29/11
to ang...@googlegroups.com
Hi,
I have implemented EventBus over a month ago:
https://github.com/angular/angular.js/pull/439
This implementation enhances scopes themselves, that code was never
pulled and it will not be - Vojta did not like the way I walked around
the leaking listeners problem, but the main reason is that the
implementation of scopes themselves are to be replaced, so my code
would not work with new scopes anyway.
However I would like to highlight that this implementation is very
very simple and it does everything EventBus should do. I believe we
should not try implement event bus as a service but rather as integral
part of scopes.

Regards,
Witold Szczerba

Igor Minar

unread,
Jul 29, 2011, 3:32:55 AM7/29/11
to ang...@googlegroups.com
Your example is also a valid case for doing it synchronously, so maybe my fear is not valid, but bear with me for a second and let's look at a different example:

eBus.on('buttonClick', function(scope) {
 delete scope.foo;
});

function MyCtrl(eBus) {
 var scope = this;
 scope.buttonClickHandler = function() {
   scope.foo = 'newVal';
   eBus.emit('buttonClick', scope);
   scope.bar = scope.foo.length;
 }
}

In this case, if a button is clicked, an exception is thrown because the listener messed with the scope. If the listener executed asynchronously, the author of the controller wouldn't have to worry about all the possible consequences of calling emit in his code.

Do you think that this is something we don't need to worry about? Or is there space for having both sync and async emitters?

/i
> --
> You received this message because you are subscribed to the Google Groups
> "angular" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/angular/-/dV2bwCUQ1b8J.

Igor Minar

unread,
Jul 29, 2011, 3:47:13 AM7/29/11
to ang...@googlegroups.com
Hi Witold,

If I didn't mention it before - thanks for the proposal, all of us had a look at it and it was a good way to get us thinking about this problem. As you said, we won't be pulling this into master because the scopes are about to be replaced with the new implementation, however there are also additional issues in the way.

Mainly, using scopes the way you did and scope's eval cycle to broadcast events is too heavy weight and creates issues that aren't obvious.

For example calling $eval() on each $fireEvent is a sure way to run into problems, especially if $fireEvent is called from within an $eval() call (controller, defer, ng:click, etc).

Additionally, it's not quite clear how the events should propagate through scope hierarchy. For example in this case:

var root = angular.scope(),
     child1 = root.$new(),
     child2 = root.$new();

child1.$on('foo', doSomething);
child2.$fire('foo', 'bar');

Should doSomething be called in this case or no? Logic would suggest that it shouldn't, but in order to make this bus practical, you likely do want it to propaga across scope boundaries. If you think otherwise, please share your argument with us.

Implementing the bus as a service, makes it clear that all events are broadcasted globally.

/i

Witold Szczerba

unread,
Jul 29, 2011, 5:35:13 AM7/29/11
to ang...@googlegroups.com
Hi, see my comments below.

On 29 July 2011 09:47, Igor Minar <iim...@gmail.com> wrote:
> Mainly, using scopes the way you did and scope's eval cycle to broadcast
> events is too heavy weight and creates issues that aren't obvious.
> For example calling $eval() on each $fireEvent is a sure way to run into
> problems, especially if $fireEvent is called from within an $eval() call
> (controller, defer, ng:click, etc).

Why is this a problem and how would you like to avoid it? What if
there is some implementation which does not explicitly call $eval on
$fireEvent? Would it mean that the result of listener execution does
not need $eval? I think whatever you would do inside listeners - they
would sooner or later trigger $eval anyway, so what is the point of
avoiding $eval from within $fireEvent?

> Additionally, it's not quite clear how the events should propagate through
> scope hierarchy. For example in this case:
> var root = angular.scope(),
>      child1 = root.$new(),
>      child2 = root.$new();
> child1.$on('foo', doSomething);
> child2.$fire('foo', 'bar');
> Should doSomething be called in this case or no? Logic would suggest that it
> shouldn't, but in order to make this bus practical, you likely do want it to
> propaga across scope boundaries. If you think otherwise, please share your
> argument with us.

This is almost exactly the case from my scenario test: $fire('foo',
...) should trigger child1's 'doSomething' - this is exactly what
event bus is for - to allow inter-scope communication. There is no
need for event bus to trigger something from within the same scope.
My exact use case was described in details on this form, that was when
I realized Angular does not support circular dependencies and event
bus was to come with a rescue:
http://groups.google.com/group/angular/browse_thread/thread/8bdef918969734c1
and this:
http://groups.google.com/group/angular/browse_thread/thread/20550bd1ffa24c87


> Implementing the bus as a service, makes it clear that all events are
> broadcasted globally.
> /i

This is how I wanted to implement event bus at the beginning, but
scopes live their own lives and singleton service stays forever, so I
figured out it would be easier not to bother with service doing
life-cycle tracking of scopes, but let the event bus be part of scopes
instead. Scope dies - so the event callback dies within.

Regards,
Witold Szczerba

Vojta Jina

unread,
Jul 29, 2011, 5:37:49 AM7/29/11
to ang...@googlegroups.com
I don't see this second example as a problem...

You can produce the same exception even with async events, as there could be another method in the controller, called some time after this event...
So let's say, your example uses async events - so no exception, right ?
And then, user clicks on <a href ng:click="error()">, where the controler's error method is:
this.error = function() {
  var l = this.foo.length;
};

And you will get the same exception... this has nothing to do with sync/async events...
V.

Vojta Jina

unread,
Jul 29, 2011, 6:01:12 AM7/29/11
to ang...@googlegroups.com
Hi Witold,

I know you did implement the event bus and thanks a lot for that. We really appreciate that. You are right, that we are not going to merge it, as it's based on old scope (and couple of other issues), but it already helped us a lot, really !
I was thinking in the same way as you, but now I feel like separating this logic into service is better idea. What are the issues with having event bus as a service ?

Some reasons:
- easier handling global events (inter-scope communication even for)
- clear api / separation - no messing up scope
- performance ? - scope is crucial place, we can do some perf tests (there is test env for that in angular), but I believe your implementation is bit heavy...

The main cons I saw, was removing dead listeners (when scope is removed), but now I kinda think it's not such a deal...

Thanks for your ideas...

V.

Witold Szczerba

unread,
Jul 29, 2011, 8:31:13 AM7/29/11
to ang...@googlegroups.com
Hi Vojta!
See my comments below.

On 29 July 2011 12:01, Vojta Jina <vojta...@gmail.com> wrote:
> I was thinking in the same way as you, but now I feel like separating this
> logic into service is better idea. What are the issues with having event bus
> as a service ?
> Some reasons:
> - easier handling global events (inter-scope communication even for)
> - clear api / separation - no messing up scope
> - performance ? - scope is crucial place, we can do some perf tests (there
> is test env for that in angular), but I believe your implementation is bit
> heavy...

ad.1) I am not sure if handling global events (do we have other kinds
of events than global?) is anyhow easier in service than in scope:
a) each scope has a reference to root
b) root can propagate to all the children
ad.2) That is questionable if API would actually be clearer, I think
the opposite:
a) first of all you would have to inject eventbus service
b) eventbus service would require 'scope' parameter (wouldn't it?)
So, instead of simple:

function MyCtrl() {
this.$onEvent('eventName', eventListener);
..
this.$fireEvent('otherEvent', parameter1, parameter2);
}

We would have to write:
function MyCtrl($eventbus) {
eventbus.on(this, 'eventName', 'eventListener');
..
eventbus.fire(this, 'otherEvent', parameter1, parameter2);
}

Do we actually require the 'this' parameter to be a scope object?
Could we get rid of the 'this' parameter? If yes - how would we know
when scope goes into oblivion? If 'this' could be something else - how
would the eventbus service figure it out? Like this: if source is an
instance of scope then track this scope and clean once it is gone?

I think scopes are so integral part of it: they emit events, they
handle event callbacks... Separating events would make sense if they
could be used outside of scopes but could they?

Thanks for exchange of opinions :)

Regards,
Witold Szczerba

> The main cons I saw, was removing dead listeners (when scope is removed),
> but now I kinda think it's not such a deal...
> Thanks for your ideas...
> V.
>

> --
> You received this message because you are subscribed to the Google Groups
> "angular" group.

> To view this discussion on the web visit

> https://groups.google.com/d/msg/angular/-/i4HTUMKe5U0J.

rur

unread,
Jul 29, 2011, 7:20:19 PM7/29/11
to angular
Brilliant stuff guys!

I've been following all your comments and I have just pushed a rework
of the eventBus code. The API remains the exactly the same.

https://github.com/rur/angular-eventbus/blob/master/app/js/services.js

[Vojta] Really appreciate that feedback. I've done my best to rework
the code as you suggest. Let me know if there are any tricks I'm
missing. I'm much happier with it now anyway.

[Igor] I made that 'root = this' change thanks! Interesting point
about the Async event broadcast. I don't think there is necessarily a
need to work this into the API though. Surly something similar can be
achieved likes so:
bus.on("evalDone", onEvalDone );
function onEvalDone(){ bus.emit("myEvent");}

I have been considering including priority... am of two minds on it.
What do you guys think?

Excuse the lack of Unit Tests, a big faux pas I know! Just a bit of
newbie apprehension I'm afraid, I'll be spending some time now getting
familiar with it and I will push some up, I promise ;-)

[Witold] I have been following your points with great interest.
Regarding service based vs. scope based events; I think if we fleshed
out the use cases for each we might find that they are quite distinct,
in which case there would be an argument to have both! Main
distinction being event propagation which, from my understanding, is
mainly useful for user interaction rather than application level
events.

As far as the API is concerned I don't see how this code is a problem:

var bus = eventBus(this);
bus.emit("event")
bus.on("event", onEvent );

Here 'this' is not required, if omitted it just means you will have to
call removeAll yourself. Event Bus is independent from any scope. As
for the injection, I think it's a good thing. The fact that a
controller/service is dependent on the global event bus should not be
kept a secret!

I'm picking up tons of insight from you guys, keep it coming!

rur



On Jul 29, 1:31 pm, Witold Szczerba <pljosh.m...@gmail.com> wrote:
> Hi Vojta!
> See my comments below.
>

Tim Cunningham

unread,
Jul 29, 2011, 7:49:47 PM7/29/11
to ang...@googlegroups.com
Nice work Rur!

Sent from my iPhone

Vojta Jina

unread,
Jul 30, 2011, 9:47:13 AM7/30/11
to ang...@googlegroups.com
Hi guys,

thanks for update rur !

I'm currently thinking about using $eventBus for more things now - for example, let's say $xhr could emit global events like "$xhr.start", "$xhr.complete", "$xhr.error", "$xhr.success"...
The advantage over adding these events directly into $xhr, like this: $xhr.onComplete(... called whenever any request is completed...);
is, that when using $eventBus, we don't have to deal with removing dead listeners, $eventBus knows how to deal with it...

Then it would make sense to allow, using $eventBus service directly (at least for emitting events), so that service like $xhr could do:
$eventBus.emmit('$xhr.complete', ...);
instead of:
var bus = $eventBus();
bus.emmit('$xhr.complete');

Using "child-event bus" makes sense only for objects, which get destroyed (controllers)...

Here simple design of that, based on your code: https://gist.github.com/1115537
See this comparison for removing subset of listeners (related to particular child-event bus / scope): http://jsperf.com/remove-all-items-from-array

Note to unit testing: I strongly believe it's better to write tests first, because:
  • it's much easier / fun
  • writing tests after implementation is boring
  • it's important to see the test failing first - so that you know that the error msg says enough information

Regards,
V.

Ruaidhri Devery

unread,
Aug 6, 2011, 1:26:03 PM8/6/11
to angular
Thats great Vojta

Sorry for the lack of updates, bit of a sh*t storm in work, not
getting much sleep :-(

I'm looking forward to getting back onto it in probably about a week!

Thanks,

rur



On Jul 30, 2:47 pm, Vojta Jina <vojta.j...@gmail.com> wrote:
> Hi guys,
>
> *thanks for update rur !*
>
> I'm currently thinking about using $eventBus for more things now - for
> example, let's say $xhr could emit global events like "$xhr.start",
> "$xhr.complete", "$xhr.error", "$xhr.success"...
> The advantage over adding these events directly into $xhr, like this: $xhr.onComplete(...
> called whenever any request is completed...);
> is, that when using $eventBus, we don't have to deal with removing dead
> listeners, $eventBus knows how to deal with it...
>
> Then it would make sense to allow, using $eventBus service directly (at
> least for emitting events), so that service like $xhr could do:
> $eventBus.emmit('$xhr.complete', ...);
> instead of:
> var bus = $eventBus();
> bus.emmit('$xhr.complete');
>
> Using "child-event bus" makes sense only for objects, which get destroyed
> (controllers)...
>
> Here simple design of that, based on your code:https://gist.github.com/1115537
> See this comparison for removing subset of listeners (related to particular
> child-event bus / scope):http://jsperf.com/remove-all-items-from-array
>
> Note to unit testing: I strongly believe it's better to write tests first,
> because:
>
>    - it's much easier / fun
>    - writing tests after implementation is boring
>    - it's important to see the test failing first - so that you know that

Ruaidhri Devery

unread,
Aug 6, 2011, 2:37:44 PM8/6/11
to angular
Thanks for that Vojta, lots of good tips in there!

Sorry for the inactivity on this, just in the middle of a s**t storm
in work.

I'm looking forward to getting back up to speed on Angular in a week
or so.

Thanks,

rur



On Jul 30, 2:47 pm, Vojta Jina <vojta.j...@gmail.com> wrote:
> Hi guys,
>
> *thanks for update rur !*
>
> I'm currently thinking about using $eventBus for more things now - for
> example, let's say $xhr could emit global events like "$xhr.start",
> "$xhr.complete", "$xhr.error", "$xhr.success"...
> The advantage over adding these events directly into $xhr, like this: $xhr.onComplete(...
> called whenever any request is completed...);
> is, that when using $eventBus, we don't have to deal with removing dead
> listeners, $eventBus knows how to deal with it...
>
> Then it would make sense to allow, using $eventBus service directly (at
> least for emitting events), so that service like $xhr could do:
> $eventBus.emmit('$xhr.complete', ...);
> instead of:
> var bus = $eventBus();
> bus.emmit('$xhr.complete');
>
> Using "child-event bus" makes sense only for objects, which get destroyed
> (controllers)...
>
> Here simple design of that, based on your code:https://gist.github.com/1115537
> See this comparison for removing subset of listeners (related to particular
> child-event bus / scope):http://jsperf.com/remove-all-items-from-array
>
> Note to unit testing: I strongly believe it's better to write tests first,
> because:
>
>    - it's much easier / fun
>    - writing tests after implementation is boring
>    - it's important to see the test failing first - so that you know that

Vojta Jina

unread,
Aug 8, 2011, 11:46:56 AM8/8/11
to ang...@googlegroups.com
Sounds good.
V.

Igor Minar

unread,
Aug 24, 2011, 3:30:53 AM8/24/11
to ang...@googlegroups.com
Hi guys,

Thanks for all the input. After a lot of experimentation and looking at your prototypes we decided to implement the event system as a part of the scope (but not tied to the $watch/$eval life-cycle).

Please check it out and comment on the pull request here: https://github.com/angular/angular.js/pull/522/files

Here is a brief overview:
- added $on, $emit, $broadcast and $removeListener scope methods for adding listeners, firing events and unregistering listeners
- $on is api to register listeners
- $emit fires an event that bubbles from the scope on which it was fired up to the root scope and triggers registered listeners along the way. this event is cancelable.
- $broadcast fires an event that propagates too all direct and indirect children of a scope on which it was fired. this even is not cancelable. this api is suitable for the event bus like use
- since the listeners are registered with a particular scope, they automatically go away when the scope is destroyed

Why didn't we go with a service as it was proposed by Ruaidhri? Well, the api is not simple to use and the risk of using it incorrectly and causing memory leaks is quite big. That's why we decided to implement the event system as something closely aligned with the scope and it's memory management.

Check it out and let us know what you think...

/i



On Mon, Aug 8, 2011 at 8:46 AM, Vojta Jina <vojta...@gmail.com> wrote:
Sounds good.
V.

--
You received this message because you are subscribed to the Google Groups "angular" group.
To view this discussion on the web visit https://groups.google.com/d/msg/angular/-/UqWLOFvtk3sJ.

Ruaidhri Devery

unread,
Aug 24, 2011, 10:21:51 AM8/24/11
to angular
Thats great, really like the idea of having bubbling events through
the scope. Makes sense when there exists a hierarchy; reminds me of
this brilliant article I read about a year ago:
http://www.javaworld.com/javaworld/jw-07-2000/jw-0721-hmvc.html

I still have a few ideas based on the eventBus service prototype so
I'm going to keep the project live on GitHub. I've updated the current
version just implementing Vojtas design. I plan to branch off a few
experiments from that project.

I've a couple of questions:
- as a matter of interest are you guys still planning on implementing
a destructor approach to GC on scopes?
- how much can be done with the inbuilt injector in angular, is it
bound strictly to services or can its use be expanded upon?

Thanks,

rur

Misko Hevery

unread,
Aug 24, 2011, 10:53:01 AM8/24/11
to ang...@googlegroups.com
- as a matter of interest are you guys still planning on implementing
a destructor approach to GC on scopes?

To have GC, one needs weak maps, which are currently not available in JS. We do have $destroy event when the scope gets deleted.
 
- how much can be done with the inbuilt injector in angular, is it
bound strictly to services or can its use be expanded upon?

We have been discussing this quite a bit, and are open to suggestions. We would like to make the injector more generic, and possibly asynchronous. We welcome any suggestions, as to what you would like to get out of it.

rur

unread,
Aug 24, 2011, 7:29:55 PM8/24/11
to angular
I think event triggered commands could be very useful. It's an nice
way of expanding on the logic of the event bus to be able to have
behavior associated with an event where the code does not comfortably
sit within any particular controller/scope. This would require fairly
robust DI to work well.

In my mind the event would trigger the creation of a command, which
gets given what it needs, does work and disappears.

What do you think?

Misko Hevery

unread,
Aug 24, 2011, 9:03:28 PM8/24/11
to ang...@googlegroups.com
Definitely cool idea, but do you have an actual use case for it? We are little hesitant to add things just because they are cool.
Reply all
Reply to author
Forward
0 new messages