I know this isn't exactly an issue, but I think it is a good question. I think of your controllers as controllers for components on a page.
How would you suggest to communicate between more controllers?
I have two components List (data rendered in a list) and ListFilter (bunch of selects and text inputs). List is loaded using AJAX. It should be reloaded every time ListFilter changes. I would like to use separate controllers for List and ListFilter to keep files small and organized.
So, how to trigger a List reload when ListFilter changes?
From a Controller instance, you can call this.application.getControllerForElementAndIdentifier:
import { Controller } from "stimulus"
export default class ListFilterController extends Controller {
// ...
reloadList() {
this.listController.reload()
}
get listController() {
return this.application.getControllerForElementAndIdentifier(this.element, "list")
}
}
Thanks! I think this is exactly what I was looking for.
I suppose I should pass the element List component is on as first argument for getControllerForElementAndIdentifier, right?
So when a List is next sibling to a ListFilter in the DOM, I should call getControllerForElementAndIdentifier(this.element.nextElementSibling, "list") to get the ListController instance, right? Or would you suggest different way to find the List's element?
I wouldn’t suggest relying on the relative orders of HTML elements.
Prefer targets to access particular elements. Can you move the data-controller="list-filter" annotation to a shared parent element and make the list element a target?
Yeah, I can do that. This look like a better way to go, thanks.
If it wouldn't be possible the only way I can think of is to create a parent Controller with targets on both List and ListFilter. Then I would override the connect function of the parent Controller to pass ListController instance to ListFilterController instance.
However, I am not sure whether ListController and ListFilterController are already connected when the connect function for the parent Controller is called. Do you have any idea about this, please?
FWIW T3 (another JS framework for the HTML you already have ;) has a simple messaging system for communicating between modules (or controllers), keeping them loosely coupled. I wonder if this is something that Stimulus might consider?
The T3 equivalent would look something like:
Box.Application.addModule('list-filter', function (context) {
return {
onchange: function () {
context.broadcast('listfilterchanged', { filterParam1: value1, … })
}
}
})
Box.Application.addModule('list', function (context) {
function reload (params) {
// reload list with AJAX
}
return {
onmessage: {
listfilterchanged: reload
}
}
})
That way other controllers could respond to the listfilterchanged event
We don’t have anything built in for this right now. The easiest thing to do is probably to emit a DOM event from the inner controller and observe it with an action from the outer controller.
What I would like to see eventually is a data-outlet attribute that connects delegate properties on a child controller directly to a containing element’s controller. Something like Interface Builder outlets in Cocoa.
Closing this issue for now, but feel free to continue discussing it here.
Could someone please explain exactly what the parameters to getContextForElementAndIdentifier are, what they mean and why I need both an element and a string as parameters? Why can't I find a controller simply by its string name e.g. "hello"?
If I have two controllers, 'hello' and 'out' and I want to get to the 'out' controller from inside the 'hello' controller. It seems that I need to pass both parameters, otherwise I get null. And only the combination of this.application.getControllerForElementAndIdentifier(this.element, "hello") work - which just gets me the controller I'm already in. What exact two parameters do I need to pass to get to the other controller named "out" - and why can't I just use the string name of that controller?
A bit of background: I too am trying to talk from one controller to another. My first controller wants to output some information in an abstract way, so wants to talk to another controller's write() method, which will do something cool with it. I'm hoping this is the sort of use case stimulus is good for?
First parameter is element the controller is connected to and the second parameter is name of the controller you want to get instance of.
You need first parameter because controller of the same name can be connected to more elements on the page. So the function needs to know where to look for the controller instance you want to get.
And you need second parameter to specify controller's name because there can be more than one controller connected to one element.
So, one way to do this is to nest components and then handle this in the parent controller as @georgeclaghorn suggested. Second clean way I can think of without relying on access to particular elements would be to add a listener to document for custom event in one component and fire this event in the second one.
We don’t have anything built in for this right now. The easiest thing to do is probably to emit a DOM event from the inner controller and observe it with an action from the outer controller.
How should one do that?
We don’t have anything built in for this right now. The easiest thing to do is probably to emit a DOM event from the inner controller and observe it with an action from the outer controller.
How should one do that?
Here's an example: https://github.com/stimulusjs/stimulus/issues/200#issuecomment-434731830
I started to work on something for stimulus, check it out, https://github.com/kinnrot/telepub tell me what you think
I've been using a pattern for several months that allows me to access the controller instances attached to DOM elements without having to call getControllerForElementAndIdentifier, a method which isn't available outside of a Stimulus controller (as you need access to the application scope) - making it inaccessible from a jQuery plugin or the console window.
Just put this into your controller's connect() method:
this.element[this.identifier] = this
This means that if you attach a comments controller to a DOM element, all you need to do to access the internal scope of your controller is element.comments. This might not be what some folks mean by communication so emitting events is still your best bet for those scenarios.
I wrote about it at length in this post: https://leastbad.com/stimulus-power-move
I offer a slightly longer version that automatically camelCases the controller identifier, too.
@leastbad thanks for that, really helpful and feels good when using it.
We are using namespaced controllers so we have something like namespace--controller, your suggested camelize function did not worked for us here.
We have implemented a different one:
export function camelize(str: string) {
return str.split(/[-_]/).map(w=> w.replace(/./, m=> m.toUpperCase())).join("").replace(/^\w/, c => c.toLowerCase());
}
Great catch, @forelabs. I'm always excited to see bullet-proof code broken. :grinning:
I'm going to borrow your code and update my post.
Just put this into your controller's
connect()method:this.element[this.identifier] = this
If you do this, and I don't recommend that you do, be very careful with your controller names. If they conflict with any of the element's own property names (there are hundreds!) things will go 💥. For example:
// value_controller.js
export default class extends Controller {
connect() {
this.element[this.identifier] = this
}
}
<input type="text" data-controller="value">
👇

To be on the safe side you could put stimulus in front or any other unique identifier. But good to hint that.
Hey @javan,
Your advice is solid. People should definitely careful to test whatever objects they attach to for potential namespace collisions. If you check out the original blog post on the technique, you'll see that I'm careful to explain to developers that they can use any valid string.
All of that said, I am a bit bummed that you seem to be throwing shade on a useful technique without offering a superior approach. Again, getControllerForElementAndIdentifier only works when you're already inside of another controller.
You can use DOM events for cross-controller communication, but that's not really a substitute for accessing the internal state of a controller.
A different take on this problem of inter-controller communication. Some sample code:
// dashboard_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
connect() {
this.name = "Simon"
$(document).on('dashboard.state', function(event, callback) {
callback(this)
}.bind(this))
}
}
And in the controller that wants to grab information from the first controller:
// sidebar_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
getDashboardState() {
$(document).trigger('dashboard.state', function(dashboardState) {
// Your code to handle the state
})
}
}
Please excuse my use of jQuery, but wanted to share the concept of it (source).
Hey Simon, that's actually pretty interesting. Do you have a solution in mind for accessing multiple instances of a controller? One thing that I like about assigning the controller instance to an accessor on the DOM element (call it the sharp knives approach) it that it allows multiple instances of a controller in the DOM... and heck, multiple controllers on an element, potentially communicating through the one thing they all have in common.
jQuery isn't itself a bad thing, and it's a reality for many projects. I actually like that Stimulus provides a viable onramp for jQuery devs that suddenly find themselves in a world where they have a legacy codebase that isn't responding well to webpack and Turbolinks. The biggest strike against jQuery in this regard is that it has its own proprietary events implementation. I did what I could to make the problem easier by releasing jquery-events-to-dom-events.
The nice approach suggested by @leastbad should be part of the the StimulusJS documentation! (possibly with all the mentioned notes regarding multi-word names, namespace, collision with existing properties. I personally use this.element.controller = this since I only have a single controller attached to that element).
It took me some days to get to this good solution. (first trying all the hacky and questionable solutions including getControllerForElementAndIdentifier)
Oh c'mon I don't know the reason why author still didn't mention https://github.com/stimulus-use/stimulus-use/blob/master/docs/application-controller.md but it works like a charm! :) (passing events from child to parent)
Based on some of the previous suggestions, I went with the approach of using custom events to let child controllers register with the parent controller. I followed the pattern of static declarations (targets, classes, and values) and added declarations for parent and children.
Here's what the controllers might look like:
class ItemsController extends ApplicationController {
static children = ['item']
}
class ItemController extends ApplicationController {
static parent = 'items'
}
Like targets, this adds properties. In this example, the items controller would have itemChildren and itemChild while each item controller simply gets a parent property.
Here's a proof of concept with the full example. I'm also working on a fork, if there's any interest in a pull request.
Try to listen to dispatched events:
my_controller.js
connect() {
this.myTarget.addEventListener(`${this.identifier}:world`, this.world.bind(this), {})
}
world() {
console.log('hello world')
}
In other controller
this.myTarget.dispatchEvent(new Event('hello:world'))
make sure this.myTarget points to the same element in both controllers
Most helpful comment
From a Controller instance, you can call
this.application.getControllerForElementAndIdentifier: