I’ve got the following controller on my HTML page:
... <div data-controller="parent"> <div data-target="parent.myDiv"> <div data-controller="child"> <span data-target="child.mySpan"></span> </div> </div> </div> ...
This child controller is mapped to the following child_controller.js
class:
export default class { static targets = ["mySpan"]; connect() { document.addEventListener("myEvent", (event) => this.handleMyEvent(event)); } handleMyEvent(event) { console.log(event); this.mySpanTarget; // Manipulate the span. No problem. } }
As you can see, there is an event listener on the connect()
of the Stimulus controller, and when it detects that the event was fired up, it logs the event and manipulates the span target.
The problem arises when I replace the contents of the target myDiv
from my parent_controller.js
:
... let childControllerHTML = "<div data-controller="child">...</div>" myDivTarget.innerHTML= childControllerHTML; ...
Now that the myEvent
gets fired up, the event listener picks it not once, but twice (because the same event got logged twice). With every subsequent replacement of the child HTML, the event gets logged one more time than it did before.
I know that one can make use of document.removeEventListener
to prevent the old controller from still listening to the events:
export default class { static targets = ["mySpan"]; connect() { this.myEventListener = document.addEventListener("myEvent", (event) => this.handleMyEvent(event)); } disconnect() { document.removeEventListener("myEvent", this.myEventListener); } handleMyEvent(event) { console.log(event); this.mySpanTarget; // FAILS. Can't find span. } }
But doing it like this makes the handleMyEvent
method lose the context
as it no longer finds the mySpanTarget
under this
.
How can I remove the listener from the child controller to which I already got no access as it is no longer in the DOM, while retaining the context?
Advertisement
Answer
I found the answer on StimulusJS’s Discourse page.
One has to make use of the bind
method when initializing the controller:
export default class { static targets = ["mySpan"]; initialize() { this.boundHandleMyEvent = this.handleMyEvent.bind(this); } connect() { document.addEventListener("myEvent", this.boundHandleMyEvent); } disconnect() { document.removeEventListener("myEvent", this.boundHandleMyEvent); } handleMyEvent(event) { console.log(event); this.mySpanTarget; // Manipulate the span. No problem. } ... }
Now, the event is only listened once, and the context is not lost inside the handleMyEvent
method.