Add and remove EventListeners

I am converting a Modal to use Stimulus.
When the modal is connected, I add a EventListener to listen for the escape key to close the modal. Thats working fine.

When I open another modal on the same page I want to remove the EventListener.
As I understand it should go in the disconnected() function.
But how do I address the ‘esc’ function there?

import { Controller } from 'stimulus'

export default class extends Controller {
  static targets = [ "wrapper" ]
  connect() {
    const esc = (e) => {
      if (e.keyCode == 27) {
        this.close();
      }
    }
    document.addEventListener("keydown", esc)
  }
  disconnect() {
    // this of course doesnt work
    // document.removeEventListener("keydown", esc)
  }
  close() {
    this.wrapperTarget.style.display = 'none'
  }
}

You need to make the event handler a member of the class to ensure it’s referenceable in both places:

import { Controller } from 'stimulus'

export default class extends Controller {
  static targets = [ "wrapper" ]

  connect() {
    document.addEventListener("keydown", this.closeHandler.bind(this))
  }

  disconnect() {
    document.removeEventListener("keydown", this.closeHandler.bind(this))
  }

  closeHandler(event) {
    if (event.keyCode == 27) {
      this.close();
    }
  }

  close() {
    this.wrapperTarget.style.display = 'none'
  }
}

Why not make it a Stimulus action instead, though? If you define your modal somewhere along these lines:

<div data-controller="modal" data-action="keydown@window->modal#close">
  <!-- ... -->
</div>

You could simplify your controller and you won’t have to manage event listeners manually:

import { Controller } from 'stimulus'

export default class extends Controller {
  static targets = [ "wrapper" ]

  close(event) {
    if (event.keyCode == 27) {
      this.wrapperTarget.style.display = 'none'
    }
  }
}
7 Likes

Great, I like your second idea.
Didnt know that you can bind to the window with:

data-action="keydown@window->modal#close"

I have the close action attached to a button in the modal as well.
I now have:

import { Controller } from 'stimulus'

export default class extends Controller {
  static targets = [ "wrapper" ]
  
  close(event) {
    if (event.keyCode == 27 || event.type == 'click') {
      this.wrapperTarget.style.display = 'none'
    }
  }
}

Thank you very much. I like how easy readable the code gets with stimulus!

5 Likes