EventBus implementation

239 views
Skip to first unread message

Witold Szczerba

unread,
Jun 22, 2011, 9:09:26 PM6/22/11
to ang...@googlegroups.com
Hi there,
Some time ago I was facing a problem with circular dependencies. Here
is an example:
$xhr depends on $xhr.error to delegate issues when handling responses
and I wanted $xhr.error, for HTTP 401, to remember the request and
resend it once user authenticates. The problem is $xhr.error cannot
depend on $xhr as it would be circular dependency. I have created a
separate login service which is called by $xhr.error, login controller
registers in that service and listens for login request message, login
form watches its controller, so it knows when to show/hide the dialog
and the controller sends 'login OK' message to the service so it can
now resends the requests using $xhr.

It works, but I was thinking about creating something more generic.

When trying to write a simple event service, I figured out that a
solution with event listeners could lead to leaks of event listeners
and its scopes. Controllers come and go, any controller could register
a listener in the service. Once the controller is gone - the event
service would still hold the reference, so each controller would have
to de-register its listener - which would be hard to achieve.

I was thinking about how does scope.$watch work - it registers a
listener, but that listener lives no longer than the scope itself -
that was very clever idea and I was thinking about similar solution
for the event bus service.

Do you have any suggestions?

Thanks,
Witold Szczerba

rur

unread,
Jun 23, 2011, 4:24:04 AM6/23/11
to angular
+1 on this

My thoughts on this one so far:
* Could we implement some kind of destructor that gets called
automatically on the controllers?
* Is there a way to enabling event bubbling through controllers?

rur

Mårten Dolk

unread,
Jun 23, 2011, 10:35:06 AM6/23/11
to ang...@googlegroups.com
+1 from me as well :-)

I had a simular issue implementing polling in one of our pages. The
page kept polling even after the user navigated to another partial.
Solved that by looking at the $location between each poll, which is
kind of a hack. Thinking about creating a general polling/scheduling
service but haven't come around to it yet... It would be useful to
have a hook when the scope dies...

/mårten

2011/6/23 rur <flui...@gmail.com>:

> --
> 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.
>
>

Igor Minar

unread,
Jun 23, 2011, 10:44:49 AM6/23/11
to ang...@googlegroups.com
We have many other use-cases for some sort of an event bus that would work in cooperation with scopes.

And are thinking how could we go about implementing it. It will likely be some kind of a scope api, so you can do:

scope.on('someEvent', function() { doSomething() });

and this listener would be active only as long as the scope on which it was defined is still active.

There are many details that we haven't sorted out yet, event propagation through scopes for example, so don't expect this in the next release just yet :)

/i




On Thu, Jun 23, 2011 at 7:35 AM, Mårten Dolk <marte...@gmail.com> wrote:
+1 from me as well :-)

I had a simular issue implementing polling in one of our pages. The
page kept polling even after the user navigated to another partial.
Solved that by looking at the $location between each poll, which is
kind of a hack. Thinking about creating a general polling/scheduling
service but haven't come around to it yet... It would be useful to
have a hook when the scope dies...

/mårten

Witold Szczerba

unread,
Jun 23, 2011, 6:56:55 PM6/23/11
to ang...@googlegroups.com
Implementing the event subscription as part of scopes' built-in
feature seems to be good approach - it resolves the problem of leaking
listeners, because they go away together with theirs scope, so there
is no problem with leaks. However, on the other side, there are the
publishers, firing events - how is the publishing mechanism know which
scopes are to be notified? Once we register them - the leaking
scopes/listeners problem is back :/

Have you found the solution to the problem?

Regards,
Witold Szczerba

P.S.
Is there something like java.lang.ref.WeakReference in JavaScript? If
so, would that be enough to get rid of the problem? What if unused
scope has not been garbage collected yet, but it still consumes
events, eating processing resources like a candies?

On 23 June 2011 16:44, Igor Minar <iim...@gmail.com> wrote:
> We have many other use-cases for some sort of an event bus that would work
> in cooperation with scopes.
> And are thinking how could we go about implementing it. It will likely be
> some kind of a scope api, so you can do:
> scope.on('someEvent', function() { doSomething() });
> and this listener would be active only as long as the scope on which it was
> defined is still active.
> There are many details that we haven't sorted out yet, event propagation
> through scopes for example, so don't expect this in the next release just
> yet :)
> /i
>
>
>
> On Thu, Jun 23, 2011 at 7:35 AM, Mårten Dolk <marte...@gmail.com> wrote:
>>
>> +1 from me as well :-)
>>
>> I had a simular issue implementing polling in one of our pages. The
>> page kept polling even after the user navigated to another partial.
>> Solved that by looking at the $location between each poll, which is
>> kind of a hack. Thinking about creating a general polling/scheduling
>> service but haven't come around to it yet... It would be useful to
>> have a hook when the scope dies...
>>
>> /mårten
>>

>> 2011/6/23 rur <flui...@gmail.com>:

rur

unread,
Jun 24, 2011, 4:18:52 AM6/24/11
to angular
Could the events propagate up from the root scope, like it does in the
DOM?



On Jun 23, 11:56 pm, Witold Szczerba <pljosh.m...@gmail.com> wrote:
> Implementing the event subscription as part of scopes' built-in
> feature seems to be good approach - it resolves the problem of leaking
> listeners, because they go away together with theirs scope, so there
> is no problem with leaks. However, on the other side, there are the
> publishers, firing events - how is the publishing mechanism know which
> scopes are to be notified? Once we register them - the leaking
> scopes/listeners problem is back :/
>
> Have you found the solution to the problem?
>
> Regards,
> Witold Szczerba
>
> P.S.
> Is there something like java.lang.ref.WeakReference in JavaScript? If
> so, would that be enough to get rid of the problem? What if unused
> scope has not been garbage collected yet, but it still consumes
> events, eating processing resources like a candies?
>
> On 23 June 2011 16:44, Igor Minar <iimi...@gmail.com> wrote:
>
>
>
>
>
>
>
> > We have many other use-cases for some sort of an event bus that would work
> > in cooperation with scopes.
> > And are thinking how could we go about implementing it. It will likely be
> > some kind of a scope api, so you can do:
> > scope.on('someEvent', function() { doSomething() });
> > and this listener would be active only as long as the scope on which it was
> > defined is still active.
> > There are many details that we haven't sorted out yet, event propagation
> > through scopes for example, so don't expect this in the next release just
> > yet :)
> > /i
>
> > On Thu, Jun 23, 2011 at 7:35 AM, Mårten Dolk <marten.d...@gmail.com> wrote:
>
> >> +1 from me as well :-)
>
> >> I had a simular issue implementing polling in one of our pages. The
> >> page kept polling even after the user navigated to another partial.
> >> Solved that by looking at the $location between each poll, which is
> >> kind of a hack. Thinking about creating a general polling/scheduling
> >> service but haven't come around to it yet... It would be useful to
> >> have a hook when the scope dies...
>
> >> /mårten
>
> >> 2011/6/23 rur <fluid...@gmail.com>:

Igor Minar

unread,
Jun 24, 2011, 2:11:19 PM6/24/11
to ang...@googlegroups.com
On Thu, Jun 23, 2011 at 3:56 PM, Witold Szczerba <pljos...@gmail.com> wrote:
Implementing the event subscription as part of scopes' built-in
feature seems to be good approach - it resolves the problem of leaking
listeners, because they go away together with theirs scope, so there
is no problem with leaks. However, on the other side, there are the
publishers, firing events - how is the publishing mechanism know which
scopes are to be notified? Once we register them - the leaking
scopes/listeners problem is back :/

scopes would handle this automatically in a similar way how evals propagate from the parent scope to child scopes. so this shouldn't be an issue.
 

Have you found the solution to the problem?

Regards,
Witold Szczerba

P.S.
Is there something like java.lang.ref.WeakReference in JavaScript? If

not yet, it is being worked on and should be in the next version of the ECMAScript spec.

/i

Igor Minar

unread,
Jun 24, 2011, 2:13:51 PM6/24/11
to ang...@googlegroups.com
Did you mean "up *to* the root scope"? That's more like the DOM event model.

We don't know how the propagation will work yet. For sure, we'll need root -> child scope propagation. I don't know just yet about the other direction, or what should happen when an event is fired from some "middle" scope.

/i
Message has been deleted

rur

unread,
Jun 25, 2011, 12:36:50 PM6/25/11
to angular
Sorry yes , 'up to' :)

In the mean time, an event bus could be written as a service and
therefore be both a singleton and injected where needed.

As I see it, this would require either one of two things: services/
controllers subscribing to the event bus would need to have a
distructor method in which they handle unsubscribing themselves, or,
even better, if the the service somehow got notified when a
controller was removed it could unsubscribe it automatically! Is any
of this possible within reason?

Thanks,

rur

Igor Minar

unread,
Jun 25, 2011, 7:36:41 PM6/25/11
to ang...@googlegroups.com
There is no way for a controller know that it's about to be killed, so there is no way to manually or automatically unsubscribe from the event bus service.

that's why I think that the event bus has to be tightly integrated with scopes and can't be just a simple service (unless you want to use it only from other services).

/i



On Sat, Jun 25, 2011 at 9:33 AM, rur <fluid.ie@gmail.com> wrote:
Sorry yes , 'up to' :)

In the mean time, a event bus could be written as a service and

therefore be both a singleton and injected where needed.

As I see this would require either one of two things: services/

controllers subscribing to the event bus would need to have a
distructor method in which they handle unsubscribing themselves, or,
even better, if the the service somehow got notified when a controller
was removed it could unsubscribe it automatically!

Is any of this possible within reason?

Thanks,

rur



On Jun 24, 7:13 pm, Igor Minar <iimi...@gmail.com> wrote:

Witold Szczerba

unread,
Jun 26, 2011, 6:22:58 PM6/26/11
to ang...@googlegroups.com
I was looking at the scope creation code and it looks like every scope
is remembered forever by its parent. It seems it leads to leaks. Once
scope registers a listener $onEval or $watch - it will be triggered
forever on root or parent's $eval. So if we have a partial with a
controller which observes something, each new instance will add its
own listener over and over again and they all will be executed
forever? Will have to check it out...
--
Witold Szczerba

On 26 June 2011 01:36, Igor Minar <iim...@gmail.com> wrote:
> There is no way for a controller know that it's about to be killed, so there
> is no way to manually or automatically unsubscribe from the event bus
> service.
> that's why I think that the event bus has to be tightly integrated with
> scopes and can't be just a simple service (unless you want to use it only
> from other services).
> /i
>
>

Witold Szczerba

unread,
Jun 26, 2011, 9:36:16 PM6/26/11
to ang...@googlegroups.com
I have created a simple test:
http://jsfiddle.net/xVMqG/18/
and wow, it looks like parent scope does not ask old, unused scopes to
evaluate during it's own $eval. How does it work? If it is not some
kind of $resource trick, then maybe this could be used 'as is' for
events propagation...
--
Witold Szczerba

Witold Szczerba

unread,
Jun 27, 2011, 10:00:14 AM6/27/11
to ang...@googlegroups.com
So, what do you think about this:
- root scope gets extra properties, e.g.:
$eventTrigger = 0; $eventPayload = null; $eventName = null;

First one is just a counter which gets incremented every time event
fires, so all the scopes can watch this property. During fire
evaluation cycle, the payload and event name are temporarily stored in
root scope. They do not need to be exposed, might be stored in some
enclosed local vars as well.

- scope.$fire(eventName, payload); looks like this:
//pseudo code
this.$fire = function(eventName, payload) {
var root = ...//root scope
try {
root.$eventPayload = payload; //no need to store it here
root/$eventName = eventName; //same as above
root.$eventTrigger += 1;
root.$eval();
} finally {
root.$eventPayload = null;
}
};

- scope.$on('evtName', function() {callback...;}); looks this:
//pseudo code:
this.$on = function(eventName, callback) {
var payload = self.$root.eventPayload; //or from somewhere else
self.$watch('$eventTrigger', function() {
if (eventName === self.$root.eventPayload) {
callback(self.$root.$eventPyload);
}
}
}

So, the user would just call scope.$on(...) to register an event
listener (in other words to subscribe) and scope.$fire(...) to fire
event (or in other words to publish an event).

What naming convention do you prefer? Register listener and fire an
event or publish-subscribe?
Do you think it might be better OK to add an option to use regexp to
match event name as an addition to simple string checking?
I would also make the event name optional, in that case it would be
the responsibility of callback to decide if it is interested or not.
Callback parameter would have to include the eventName as well. I
think it might be OK for en event callback function to receive exactly
the same parameters used in $fire instead of single payload object,
hm?

What do you think? If this looks OK to you, I could try to follow the:
http://docs.angularjs.org/#!/misc/contribute
guide to submit a pull request.

Thanks,
Witold Szczerba

Igor Minar

unread,
Jun 28, 2011, 1:55:05 AM6/28/11
to ang...@googlegroups.com
It all works via closure references to the child scope. There are no memory leaks there as you found out in your test.

We have a new scope implementation actually changes the approach and instead of closure references, we have object properties for keeping track of child nodes. This has many advantages, but also requires us to have a destructor for scopes. You can check out the new scope implementation at https://github.com/mhevery/angular.js/commit/4d0b16415e3a25655426d84264af9f32fff965c0

/i

Igor Minar

unread,
Jun 28, 2011, 2:30:42 AM6/28/11
to ang...@googlegroups.com
On Mon, Jun 27, 2011 at 7:00 AM, Witold Szczerba <pljos...@gmail.com> wrote:
So, what do you think about this:
- root scope gets extra properties, e.g.:
 $eventTrigger = 0; $eventPayload = null; $eventName = null;

First one is just a counter which gets incremented every time event
fires, so all the scopes can watch this property. During fire
evaluation cycle, the payload and event name are temporarily stored in
root scope. They do not need to be exposed, might be stored in some
enclosed local vars as well.

- scope.$fire(eventName, payload); looks like this:
//pseudo code
this.$fire = function(eventName, payload) {
 var root = ...//root scope
 try {
   root.$eventPayload = payload; //no need to store it here
   root/$eventName = eventName; //same as above
   root.$eventTrigger += 1;
   root.$eval();
 } finally {
   root.$eventPayload = null;
 }
};

why not just:

this.$fire = function(eventName, payload) {
 var root = ...//root scope
 try {
   root.$event = event;
   root.$eval();
 } finally {
   delete root.$event;
 }
};

and have child scopes watch $event?


- scope.$on('evtName', function() {callback...;}); looks this:
//pseudo code:
this.$on = function(eventName, callback) {
 var payload = self.$root.eventPayload; //or from somewhere else
 self.$watch('$eventTrigger', function() {
   if (eventName === self.$root.eventPayload) {
     callback(self.$root.$eventPyload);
   }
 }
}

the bad thing about this approach is that $event would become public and anything could tamper with it. This is not what you want. We'll need to mark it as private (in new scopes we use $$ prefix), or hide it in a closure (likely not possible).
 

So, the user would just call scope.$on(...) to register an event
listener (in other words to subscribe) and scope.$fire(...) to fire
event (or in other words to publish an event).

yeah, that's almost the kind of api that we have in mind.
 

What naming convention do you prefer? Register listener and fire an
event or publish-subscribe?

the commonjs/nodejs style on() and emit()
 
Do you think it might be better OK to add an option to use regexp to
match event name as an addition to simple string checking?
I would also make the event name optional, in that case it would be
the responsibility of callback to decide if it is interested or not.
Callback parameter would have to include the eventName as well. I
think it might be OK for en event callback function to receive exactly
the same parameters used in $fire instead of single payload object,
hm?

while it's nice to have direct access to all arguments, it ties hands to the event publisher, because the order of arguments will become the api that if changed, will break all subscribers.
 

What do you think? If this looks OK to you, I could try to follow the:
http://docs.angularjs.org/#!/misc/contribute
guide to submit a pull request.

sure, but please wait until we land the new scopes. that commit will change everything, so I don't want you to waste time with the old scopes.

cheers,
Igor

Witold Szczerba

unread,
Jun 28, 2011, 5:48:05 AM6/28/11
to ang...@googlegroups.com
Ah, yesterday, before you write your response, I have implemented
event bus in Angular (+2 unit tests). I fell asleep before sending
pull request and here it is now. I know there are work in progress to
reimplement scopes, but until that goes into master trunk, maybe it
would be worth considering adding event bus now and prepare a proper
replacement in new scopes before switch?
https://github.com/angular/angular.js/pull/439/files
I have added two new methods to scopes: $onEvent and $fireEvent, but
at the beginning it was $on and $fire and now I see I forget to rename
that in one place in documentation. Don't know how that could be done
once I have pushed my changes into GitHub and after a pull request,
hmmm...

Thanks,
Witold Szczerba

Igor Minar

unread,
Jun 29, 2011, 12:16:27 AM6/29/11
to ang...@googlegroups.com
hmm.. I'll review this tomorrow and will get back to you..
Reply all
Reply to author
Forward
0 new messages