Reputation: 823
I am trying to remove an eventListener but it looks like I miss something.
Why is the following code not working, it doesn't remove the event listener from the button.
I also tried binding this to pass the scope, but that didn't work either
class Test {
eventHandler(e) {
console.log(e.target.id)
alert()
// no effect
e.target.removeEventListener("click", this.eventHandler)
// no effect either
document.getElementById(e.target.id).removeEventListener("click", this.eventHandler)
}
constructor() {
let b = document.getElementById("b")
b.addEventListener("click", this.eventHandler)
//b.addEventListener("click", this.eventHandler.bind(this) )
}
}
new Test()
<button id="b">
click me
</button>
Upvotes: 3
Views: 1059
Reputation: 23386
Prototype methods as event handlers are a bit problematic, specifically when you need both, the this value bound to the instance, and the reference to the actual event handler function.
By default, the event queue calls the handler in the context of the element the event was bound to. It's easy to change the context, but that provides you to create a new function, which then is used as the event handler, and that function is not the method in the prototype anymore.
If you want to keep the compact class structure, one way is to define the event handler methods as own properties of the instance, they simply can't be inherited. The simplest way would be to define the methods as arrow functions in the constructor.
class Test {
constructor() {
this.eventHandler = e => {
console.log(e.target.id);
e.target.removeEventListener("click", this.eventHandler);
};
let b = document.getElementById("b");
b.addEventListener("click", this.eventHandler);
}
}
new Test();
<button id="b">Click me!</button>
The arrow function keeps the reference to the lexical environment it was defined in, and the event queue can't override the context. Now this
in the handler function is correctly bound to the instance, and this.eventHandler
refers to the function, which was attached to the event.
A slightly less memoryconsuming option would be to use bind
when creating the own property, like this:
class Test {
constructor() {
this.eventHandler = this.eventHandler.bind(this);
let b = document.getElementById("b");
b.addEventListener("click", this.eventHandler);
}
eventHandler (e) {
console.log(e.target.id);
e.target.removeEventListener("click", this.eventHandler);
}
}
Here bind
creates a new function object, which then calls the method in the prototype, the actual code of the method is not duplicated. This is loosely similar if you wrote:
this.eventHandler = e => Test.prototype.eventHandler.call(this, e);
It's notable, that when defining an own property with the same name an underlying prototype property has, the prototype property is not overridden, it's only shadowed in the instance, and multiple instances of the class will still work as intended.
Another option is to create your own "event model", which creates a wrapper function (like in the very last code example above) for all events, and stores the reference to that function. The wrapper calls the actual handler with call
, which can bind the wanted this
value to the event handler. The stored function references are used to remove events. Building such a model is not extremely complex, but it provides a bit knowledge of how the this
binding and native event model work.
Upvotes: 2
Reputation: 13356
The OP's code does not work for two reasons.
eventHandler
misses the correct this
context.this.eventHandler.bind(this)
one creates a new (handler) function with no saved reference to it. Thus with removeEventHandler
one never refers to the correct event handler.Possible solution ...
function handleTestClickEvent(evt) {
console.log(evt.currentTarget);
console.log(this);
console.log(this.eventHandler);
// remove the instance specific (`this` context) `eventHandler`.
evt.currentTarget.removeEventListener('click', this.eventHandler);
}
class Test {
constructor() {
// create own eventHandler with bound `this` context.
this.eventHandler = handleTestClickEvent.bind(this);
document
.querySelector('#b')
.addEventListener('click', this.eventHandler);
}
}
new Test();
<button id="b">click me</button>
Another possible approach was the usage of an arrow-function based, thus instance-specific, event-handler. Arrow-functions do not support an explicit this
binding. They always refer to the context where they are implemented in.
class Test {
constructor() {
// arrow-function based, thus instance-specific event-handler.
this.eventHandler = evt => {
console.log(evt.currentTarget);
console.log(this);
evt.currentTarget.removeEventListener('click', this.eventHandler);
}
document
.querySelector('#b')
.addEventListener('click', this.eventHandler);
}
}
new Test();
<button id="b">click me</button>
Nevertheless, both approaches show, that the prototypal implementation of a reference-specific event-handler is not the path one should follow.
For the scenario provided by the OP I would pick the 1st solution for it provides code reuse by the locally implemented handleTestClickEvent
. It also comes with a smaller footprint regarding the instance specific this.eventHandler
which for the former gets created from handleTestClickEvent.bind(this)
whereas the 2nd solution provides a full handler implementation to every single instance.
Upvotes: 1