Another pattern is to keep your Listener inside the constructor.
To remove an Event Listener (no matter what pattern) you can add a 'remove' function the moment you create an Event Listener.
Since the remove function is called within the listen scope, it uses the same name and function
pseudo code:
  listen(name , func){
    window.addEventListener(name, func);
    return () => window.removeEventListener( name , func );
  }
  let remove = listen( 'click' , () => alert('BOO!') );
  //cleanup:
  remove();
Run Code Snippet below to see it being used with multiple buttons
Events bubbling UP & shadowDOM
to save you an hour once you do more with events...
Note that WebComponents (ie CustomElements with shadowDOM) need CustomEvents with the composed:true property if you want them to bubble up past its shadowDOM boundary
    new CustomEvent("check", {
      bubbles: true,
      //cancelable: false,
      composed: true       // required to break out of shadowDOM
    });
Removing added Event Listeners
Note: this example does not run on Safari, as Apple refuses to implement extending elements : extends HTMLButtonElement
class MyEl extends HTMLButtonElement {
  constructor() {
    let ME = super();// super() retuns this scope; ME makes code easier to read
    let count = 0;// you do not have to stick everything on the Element
    ME.mute = ME.listen('click' , event => {
      //this function is in constructor scope, so has access to ALL its contents
      if(event.target === ME) //because ALL click events will fire!
        ME.textContent = `clicked ${ME.id} ${++count} times`;
      //if you only want to allow N clicks per button you call ME.mute() here
    });
  }
  listen(name , func){
    window.addEventListener( name , func );
    console.log('added' , name , this.id );
    return () => { // return a Function!
      console.log( 'removeEventListener' , name , 'from' , this.id);
      this.style.opacity=.5;
      window.removeEventListener( name , func );
    }
  }
  eol(){ // End of Life
    this.parentNode.removeChild(this);
  }
  disconnectedCallback() {
      console.log('disconnectedCallback');
      this.mute();
  }
}
customElements.define('my-el', MyEl, { extends: 'button' });
button{
  width:12em;
}
<button id="One" is="my-el" type="button">Click me</button>
<button onclick="One.mute()">Mute</button> 
<button onclick="One.eol()">Delete</button> 
<br>
<button id="Two" is="my-el" type="button">Click me too</button>
<button onclick="Two.disconnectedCallback()">Mute</button> 
<button onclick="Two.eol()">Delete</button> 
 
 
Notes:
- countis not available as- this.countbut is available to all functions defined IN constructor scope. So it is (kinda) private, only the click function can update it.
 
- onclick=Two.disconnectedCallback()just as example that function does NOT remove the element.
 
Also see: https://pm.dartus.fr/blog/a-complete-guide-on-shadow-dom-and-event-propagation/