How to get EventAggregator instance without using constructor injection

626 views Asked by At

I have a class that looks like this:

@inject(EventAggregator)
export class LootStack {
    stack: Array<Item.Loot> = [];

    constructor(private eventAggregator: EventAggregator) {
        this.eventAggregator.subscribe(MonsterKilled, () => {
            this.stack.push(new Item.Gold()); 
            this.stack.push(new Item.Weapon("Thunderfury, Blessed Blade of the Windseeker"));
        });
    }

    takeItem(lootItem: Item.Loot){
        this.eventAggregator.publish(lootItem.take()) <--- returns specific message
        this.stack.remove(lootItem);
    }
}

When takeItem() is called I want to publish a message based on the type of item clicked. I have hacked together a way of doing this without having the item know of the EventAggregator by getting the take() method on the item instance return the correct message.

Gold is then implemented like this: take() { return new GoldTaken(this) };

And Weapon is then implemented like this: take() { return new WeaponTaken(this) };

I then just publish this immediately via the viewmodel which is aware of the EA.

It would be nicer and clearer if the items could publish this message themselves, but in order to get the EventAggregator i only know of injecting the correct instance via the constructor. This is not so desireable as I dont want to have to pass this in every time i new up and item.

Is there a way for me to get the correct EventAggregator singleton from within my take method on the item instance?

2

There are 2 answers

2
Ashley Grant On BEST ANSWER

You could inject the event aggregator in yourself.

new Item.Gold(this.eventAggregator)

Another option would be to use Aurelia's DI container to create the items and allow the DI container to inject the event aggregator in to them for you. You would probably want to set the name property after that.

Here's an example: https://gist.run?id=e1f5a3159a1a7464140f5e3b0582c18e

app.js

import {inject, Container} from 'aurelia-framework';
import {Weapon} from './weapon';

@inject(Container)
export class App {

    constructor(container){ 
      const weapon = container.get(Weapon);

      weapon.name = 'Thunderfury, Blessed Blade of the Windseeker';

      console.log(weapon);
    } 
}

weapon.js

import {inject} from 'aurelia-framework';
import {EventAggregator} from 'aurelia-event-aggregator';

@inject(EventAggregator)
export class Weapon {
  name = '';
  constructor(ea) {
    this.ea = ea;
  } 
}
0
4imble On

Just a quick update to show how it all looked with the recommendation in the end. It's nice to call right into the items take method and not have some weird return message to handle now.

@autoinject
export default class ItemFactory {
    constructor(private eventAggregator: EventAggregator) { }

    buildGold(): Item.Gold {
        let newGold = new Item.Gold(this.eventAggregator);
        newGold.value = Dice.d20();
        newGold.template = "gold";
        return newGold;
    }

    buildWeapon(name: string): Item.Weapon {
        let newWeapon = new Item.Weapon(this.eventAggregator);
        newWeapon.name = name;
        newWeapon.damage = Dice.d20();
        newWeapon.template = "weapon";
        return newWeapon;
    }
}

Then I can simply do this from take item:

@autoinject
export class LootStack {
    stack: Array<Item.Loot> = [];

    constructor(private eventAggregator: EventAggregator, private itemFactory: ItemFactory) {
        this.eventAggregator.subscribe(MonsterKilled, () => {
            this.stack.push(itemFactory.buildGold()); 
            this.stack.push(itemFactory.buildWeapon("Thunderfury, Blessed Blade of the Windseeker"));
        });
    }

    takeItem(lootItem: Item.Loot){
        lootItem.take();
        this.stack.remove(lootItem);
    }
}

[Github Changeset]