Getting parent controller from within child

I’ve seen some discussion on how to best get a parent controller from within a child controller. I will outline my current solution here and if others find it useful, I will look at adding ‘getParentControllerForIdentifier’ via a pull request.

  getParentControllerForIdentifier(identifier) {
    return this.application.controllers.find(controller => {
      return (
        controller.context.identifier === identifier &&
        controller.element.contains(this.element)
      )
    })
  }

I like that idea in theory, but there could be many parent controllers up the tree with the same identifier, so it might be better to change it to getNearestParent…etc and ensure the code gets only the controller for the parent element closest to the child.

Good point. As there are many potential controllers, I wonder what’s the most efficient way of getting the closest parent?

There is already the undocumented function getControllerForElementAndIdentifier that you can use

some references:


1 Like

I can offer a compelling solution that does not require access to the application object eg. can be called from outside of a Stimulus controller if necessary.

First, modify your controllers so that in the connect() method you add this line:

this.element[this.identifier] = this

This means that each controller - including your “parent” - will expose their internal state via a variable attached to it’s DOM element.

Now, accessing the widget controller of your parent is a query away:

this.element.closest('[data-controller~=widget]').widget

1 Like

I was looking for an easy way to access the controller from an element. Leastbad’s suggestion seems like a clean and easy approach. An even better or at least cleaner way IMHO would be to declare this in the controller you wish to access from the DOM element:

connect() {
  this.element.controller = this;
}

This will allow you to access the controller from another controller like this:

this.element.closest('[data-controller~=widget]').controller;

Not an answer directly, but could the child controller .dispatch() an event up the DOM tree to communicate instead of having a direct parent reference? Also, there is a ?relatively new? concept of Outlets that let you identify an external controller in the DOM. I haven’t tried outlets yet, but it might be something worth looking into.

Dispatching custom events is a good way of triggering actions in one controller from another. However, in this case I am trying to grab certain properties of the parent controller, for instance:

this.parentThing = this.element.closest('.thing').controller;

// Grab a value
let someProperty = this.parentThing.propertyValue;

// Grab a property belonging to a target belonging to the parent:
let someText = this.parentThing.titleTarget.innerText;

I think the main use case here is when you have a parent that contains values that apply to all of the underlying child elements. Something like:

<ul data-controller="medialist" data-medialist-type-value="music">
  <li data-controller="mediaitem" data-action="click->mediaitem#submit">Rock 'n Roll</li>
  <li data-controller="mediaitem" data-action="click->mediaitem#submit">Soul</li>
  <li data-controller="mediaitem" data-action="click->mediaitem#submit">8 bit Gameboy generated funk</li>
</ul>

This is a silly example but just to illustrate the use case. Imagine clicking on the li triggers a process where something is submitted to the backend, and we need to include the “type=music” value from its parent in the request.

Outlets seem like an approach to this but only working from the parent down to the children, not the other way around. Unless I am missing something?

Ah, I understand what you’re saying now. I haven’t tried anything with nested controllers yet. But, I’m wondering if instead of having the mediaitem controller execute the submit, maybe the medialist controller can execute the submit and then have some sort of an “action parameter” being provided by the list items.

I’m just hand-wavey at the moment because I haven’t actually tried anything like that yet so I don’t know how the nested Controller constraints get applied. I’m just thinking out loud.

This is more for my own benefit, but our conversation inspired me to do a little cross-controller experimentation. It might be helpful for others who happen upon this thread. That said, it’s not exactly in alignment with what you’re doing - you want the child to talk directly to the parent. In my experiment, I’m calling an action directly on the parent, but I’m passing in the data as event parameters. I also have some event-dispatching stuff as well.

So, again, this doesn’t exactly speak to what you’re doing; but, it’s kind of an alternate take on the topic.

I think your approach of having the “parent controller” execute the submit is definitely a solution. In my specific case this structure/model doesn’t make complete sense though. It’s hard to explain but I guess it depends on how much the “mediaitems” are stand-alone entities that are only partly defined by being a child element of the medialist … if that makes any sense :grinning:

Great article, I’m looking forward to what direction Stimulus outlets or the general idea of hierarchy between controllers will be taking. My feeling says there’s more to explore here!

1 Like

This stuff is fun - but also frustrating - to explore. I come from an Angular background, and in Angular, you could literally just “inject” the parent controller into the child controller :smiley: So, with Hotwire I’m having to really re-think the way things relate to each other and where the data lives and how it gets passed around.