How to inject a service in discourse?

I want to use a service in a theme component I am building. If services are only available in plugins, I can build a plugin as well. Goal is to connect two different plugin outlets - press a button in one outlet, and have that button press picked up by the other outlet.

How can I properly register a service in Discourse? I’ve followed the topic here, and the related plugin code on github, but I’m not able to get it to work.

I think the missing piece is that my theme component is not recognizing the service. (Tried it in a plugin too, with same result).


Here’s some code I’ve tried:

javascripts/discourse/services/great-stuff.js:

import Service, { inject as service } from "@ember/service";

export default class GreatStuffService extends Service {
    call() {
        console.log('you called it')
    }
}

connectors/topic-list-after-title/sample-outlet.js.es6:

import { inject as service } from "@ember/service";

export default {
  greatStuff: service(),
  actions: {
      buttonPressInOutlet(){
         this.greatStuff.call() 
     }
  }
}

When I call the action “buttonPressInOutlet()” I get the error: Uncaught TypeError: Cannot read properties of undefined (reading 'call').

What else is required?

You might want to look at how other services are written. I don’t think the x extends y syntax works yet on the ember version discourse uses.

Also you might want to look at the appEvents api to communicate between two components.

2 Likes

The problem here is that the ‘connector’ is not an EmberObject, and so you can’t inject things into it. Instead, you’ll need to create an Ember Component and inject it there.

Your ‘connector’ template would then look something like

{{my-component-name}}

Here’s an example of how that’s done in the whos-online plugin:

We have a service in core:

A connector template in the plugin

The component definition:

(or you can use export default Component.extend({ whosOnline: service() }) if you prefer)

And the component template:

3 Likes

I hadn’t thought of that. Thanks for the idea!


@david: thank you very much for this explanation and code examples. With those examples, I’ve been able to get it to work to click a button in in a component, and have it call an action in the service. That’s a big step forward.

Now I’m trying to sort through the other half–once an action is called in a service, have that trigger an action in a component. I figure it’s something like, in the service, importing the component and calling a function in that component (and/or subscribing to the action in the component). But I haven’t quite got the syntax yet.

Assuming that’s right, do you have any example syntax?

Calling a component method from a service isn’t really best practice, and Ember doesn’t provide an easy way to do it. There can be multiple instances of a component, so how would the service know which one to trigger the action on?

That said, sometimes it can be necessary to get something working within the confines of the Discourse plugin outlet system.

I’d recommend the same as @fzngagan - take a look at appEvents. They’re probably the cleanest way to trigger component logic from a service.

2 Likes

I had thought that is what evented was meant for, but I haven’t used it before. The goal is just to get an ipc-like setup where

  • user clicks button in component A
  • that click causes data to load in component B

I’m more familiar with Angular, where creating then subscribing to a service would be the general way to do it. But in Discourse the best way is appEvents?

Exactly! appEvents is a wrapper for Evented. If you prefer to use Evented directly then that’s also fine :+1:

1 Like

:slight_smile: Interesting! Thanks for the follow-up. I’m not partial to evented - never used it before and I was struggling a bit with the syntax in a Discourse service:
export default class GreatStuffService extends Service.extends(Evented, {...})
is not quite right.

Haven’t used appEvents before either. Am I able to create a new appEvent in a plugin/theme-component that I can then subscribe to? Most examples I am finding are about subscribing to appEvents that are set out already in Discourse core.

1 Like

Yes you can do that. lookup appEvents.trigger

2 Likes