The Event Aggregator is one of my favourite things about Aurelia and it is not even anything unique to Aurelia.
There does not seem to be much info out there about it, probably due to its simplicity. But I have noticed people ask about it in the Gitter chat from time-to-time. At its core the Event Aggregator is a pub/sub layer for publishing and listening to actions that take place inside of your application loosely.
If you look at the source of this module for Aurelia, you will see it is actually super simple to understand and how it works.
When to use the Event Aggregator
Like choosing to use anything, the decision to use the Event Aggregator module should be dictated by your application requirements. If your application requires knowing about what other parts are doing at various times in your application, the Event Aggregator can be incredibly helpful.
It allows you to fire off events that don’t have a specific target, events potentially multiple listeners could be looking out for. They are not essential, they are like observers, when something happens the subscriber methods are notified and allow you to act accordingly.
Everything comes with a cost, so don’t abuse it and use it for every change in your application, it is very much for those situations where cross component communication is essential.
Using the Event Aggregator
The Event Aggregator only has three exposed methods and they are incredibly easy to understand. Like any other Aurelia module, you import and inject it into your view model before using it.
import { inject } from 'aurelia-framework';
import { EventAggregator } from 'aurelia-event-aggregator';
@inject(EventAggregator)
export class MyClass {
constructor(EventAggregator) {
this.ea = EventAggregator;
}
}
All of the below descriptions of the Event Aggregator methods will use the above theoretical example of the Event Aggregator being injected and aliased on the class through injection as ea
.
publish(event, data?)
The publish method allows you to fire off events. These events don’t have a specific target, they are loose events fired off into space. They don’t care who subscribes to them, there are no constraints on them.
The first argument is the name of the event, you can choose whatever name you want. For the below example, I chose “puppyMonkeyBaby” for no reason at all.
The second argument is data you want to supply along with your event and it is completely optional. Most of the time it will be an array or object of data. You can even just pass through a string value if you like as well. However, not all events need to supply data.
this.ea.publish('puppyMonkeyBaby', {testValue: 'What just happened?'});
subscribe(event, callbackFunction)
This is the method we use to listen to our published event. Taking the above creepy example of “puppyMonkeyBaby” lets get the value that was sent back, which was an object with one value.
The first parameter is the name of the event we are listening out for. The second argument is a function and in the below instance, we are using an arrow function to retain our view model scope for this
context.
The callback function returns the supplied value as a parameter. In our case, it is an object, but if it was a string, it would be a string.
let subscription = this.ea.subscribe('puppyMonkeyBaby', response => {
console.log(response);
// This should yield: Object {testValue: "What just happened?"}
});
The above is the same as:
let subscription = this.ea.subscribe('puppyMonkeyBaby', function(response) {
console.log(response);
// This should yield: Object {testValue: "What just happened?"}
});
Worth pointing out is the subscribe method returns a function called dispose. This allows you to clean up and remove the subscriber once you are done with it. This might be in most cases inside of the detached
lifecycle method of your view model.
Subscribe in action with dispose
Below is an example of a subscription that is removed when the view model is detached. This is a garbage collection measure and ensures your app does not use resources it no longer requires.
import { inject } from 'aurelia-framework';
import { EventAggregator } from 'aurelia-event-aggregator';
@inject(EventAggregator)
export class MyClass {
constructor(EventAggregator) {
this.ea = EventAggregator;
}
attached() {
this.subscriber = this.ea.subscribe('puppyMonkeyBaby', response => {
console.log(response.testValue);
});
}
detached() {
this.subscriber.dispose();
}
}
subscribeOnce(event, callbackFunction)
We won’t be giving any examples of this one in action. It is exactly the same as the above method, except this is a one time subscription. Once the event fires, the subscription is disposed of and never fired again.
This might be handy for situations where you only need to know if something happened once, like a page load.
Aurelia dependencies that use Event Aggregator
Various Aurelia dependencies actually use the Event Aggregator to communicate events taking place you can subscribe too within your app.
Because Aurelia is decoupled and can be built up with numerous dependencies, it makes Event Aggregator the ideal candidate.
Aurelia i18n
The translation dependency for Aurelia which allows you to support multiple languages in your application uses the Event Aggregator to communicate when the locale has changed here.
Aurelia Router
The router dependency is a great example of the Event Aggregator in use. You can see events being fired through the aggregator here. It allows you to listen for successful and unsuccessful route changes when the router is used.
If you want to see how the Event Aggregator works with the Router, I have written a convenient post here which describes how to do it.
If you are a plugin author, consider whether it would benefit you or make sense to use the Event Aggregator as well.
Would this be a good way to deal with updating UI components in conjunction with SignalR (or something similar). I’d like to have, what Meteor terms as 3 way data binding, in my application but I’m not sure the best way to set that up. I’d like to update the GUI but then have all other clients connected to the app also get the update. Any ideas on how to best accomplish that?
How would you compare this to regular events from your other post “Aurelia Custom Elements & Custom Callback Events (tutorial)”?
I guess there are different use-cases? E.g. for a list with custom elements for each row, where the rows need to use list-wide functionality available in the outer list, would you use EA or regular events? (e.g. delete row)
Not working…
i get “TypeError: this.ea.subscribe is not a function(…)” in the subscriber class.
Any ideas?
@Dwayne – there is a problem with the code:
You shouldn’t be injecting a thing using the name of the class as the parameter name injected into the constructor (well, not in es2015, at least – I don’t know how typescript handles it)
import { EventAggregator } from ‘aurelia-event-aggregator’;
@inject(EventAggregator)
export class MyClass {
constructor(eventAggregator) {
this.ea = eventAggregator;
}
FYI if you are having issues importing event aggregator. If it is looking in the /dist folder for event aggregator:
http://stackoverflow.com/questions/38780837/aurelia-eventaggregator-does-not-import-correctly
Hi Dwayne. As always thanks for your incredibly concise and (with a little bit of prior knowledge) easy to follow tutorials. I promise to be be a patreon in the forseeable future.
As Mr. Abrahamsen I followed your former tutorial concerning Custom Events with delegates in Aurelia and I’d also like to know if you’ve got an opinion of when to use EventAggregator vs. CustomEvents. Not pushing here, as I guess it’s a matter of personal preference (easy vs. exact?) and performance would come into play only with many publishers/subscribers.
I’d also like to hear about CustomEvent vs EventAggregator