Search code examples
javascriptaddeventlistenereventhandlerremoveeventlistener

How can I remove the event listeners from within a javascript class?


I already saw this thread, but I think my case is a bit different.

This is an example class, whose constructor receives an element as parameter, that can be moved dragging it. But it does not stop being dragged on mouse up. If I good understood what I read in the other thread, I guess I have to assign the methods to variables, but I don't know how, as they take the event parameter:

class Example
{
    rect;

    constructor(rect)
    {
        this.rect = rect;
        this.rect.addEventListener('mousedown', event => this.onMouseDown(event));
    }

    onMouseDown(event)
    {
        document.addEventListener('mousemove', event => this.onMouseMove(event));
        document.addEventListener('mouseup', event => this.onMouseUp(event));
    }

    onMouseMove(event)
    {
        this.rect.style.left = event.clientX;
        this.rect.style.top = event.clientY;
    }

    onMouseUp(event)
    {
        document.removeEventListener('mousemove', this.onMouseMove);
        document.removeEventListener('mouseup', this.onMouseUp);
    }
}

document.addEventListener("DOMContentLoaded", function()
{
    var rect = document.getElementById('rect');
    var ex = new Example(rect);
});
#rect {
    position: absolute;
    border: 1px solid #ccc;
    background-color: #DFD;
    width: 100px;
    height: 100px;
}
<html>
    <head>
        <meta charset="utf-8"/>
        <title>Test</title>
    </head>

    <body>
        <div id="rect"></div>
    </body>
</html>

I don't know why the snippet here does not work, I copied the code from some files, that perfectly work.


Solution

  • TL;DR the easiest thing to do here is this.rect.addEventListener('mousedown', this.onMouseDown); and change your class methods to fat arrow functions onMouseDown = (event) => {...}

    The problem

    When you add an event listener, you need to keep a reference to the function you added to remove it. When you do this:

     document.addEventListener('mousemove', event => this.onMouseMove(event));
    

    You're creating an entirely new function using the => "fat arrow" operator. Your code is equivalent to this:

    onMouseDown(event) {
        const listener = event => this.onMouseMove(event);
        document.addEventListener('mousemove', listener);
    }
    

    You can see that the listener function only exists inside the onMouseDown method. There's no way to access it in other methods.

    So when you run this line, later:

    document.removeEventListener('mousemove', this.onMouseMove);
    

    Nothing happens, because the reference to your method this.onMouseMove is not the same as listener. They are different functions.

    Solution(s)

    You should research this, and methods vs functions in Javascript. This type of issue comes up a lot when dealing with classes and events.

    There a few ways to solve this problem. First of all, change your code to bind the class method, so that we can remove it later:

    this.rect.addEventListener('mousedown', this.onMouseDown);
    

    But there's a problem here. When you pass in this.onMouseDown to addEventListener, you're binding the event listener to the function onMouseDown. When you do this, it becomes a "function," not a "method". It loses its relationship to the Example class, so that when onMouseDown runs, this will no longer be the instance of Example. So when the code executes with:

    document.addEventListener('mousemove', event => this.onMouseMove(event));
    

    this will no longer point to the class instance, and the code will error rather than add an event listener.

    A handy trick in Javascript to get around this issue is to instead define methods with the fat arrow operator, which makes them always use the parent's this, which will be your class instance. Here's the full code:

    class Example {
        rect;
    
        constructor(rect) {
            this.rect = rect;
            this.rect.addEventListener('mousedown', this.onMouseDown);
        }
    
        onMouseDown = () => {
            document.addEventListener('mousemove', this.onMouseMove);
            document.addEventListener('mouseup', this.onMouseUp);
        }
    
        onMouseMove = (event) => {
            this.rect.style.left = event.clientX;
            this.rect.style.top = event.clientY;
        }
    
        onMouseUp = (event) => {
            document.removeEventListener('mousemove', this.onMouseMove);
            document.removeEventListener('mouseup', this.onMouseUp);
        }
    }