3

I am trying to removeEventListener in my angular compenent: Javascript removeEventListener not working

    ...
    ngOnInit() {
        document.addEventListener('visibilitychange', () =>this.handleVisibleState(), true);
    }

    ngOnDestroy() {
        document.removeEventListener('visibilitychange', () => this.handleVisibleState(), true);
    }

    handleVisibleState() {
        let vis = document.visibilityState === 'visible';
        this.configsService.update_collab_visible(vis);
    }
    ...

with the above addEventListener works even after ngOnDestroy ()

How can I unbind visibilityState from document in angular components?

attempt 2

    private visibilityChangeCallback: () => void;

    ngOnInit() {
        this.visibilityChangeCallback = () => this.handleVisibleState();
        document.addEventListener('visibilitychange', this.handleVisibleState, true);
    }

    ngOnDestroy() {
        document.removeEventListener('visibilitychange', this.handleVisibleState, true);
    }

    handleVisibleState() {
        let vis = document.visibilityState === 'visible';
        console.log(typeof this.configsService); // undefined
        this.configsService.update_collab_visible(vis);
    }

result:

ERROR TypeError: Cannot read property 'update_collab_visible' of undefined

Omar
  • 2,726
  • 2
  • 32
  • 65

4 Answers4

5

Store the callback:

private visibilityChangeCallback: () => void;

ngOnInit() {
    this.visibilityChangeCallback = () => this.handleVisibleState();
    document.addEventListener('visibilitychange', this.visibilityChangeCallback, true);
}

ngOnDestroy() {
    document.removeEventListener('visibilitychange', this.visibilityChangeCallback, true);
}
Frank Modica
  • 10,238
  • 3
  • 23
  • 39
  • I guess `self.handleVisibleState()` should be `this.handleVisibleState()` – Sunil Singh Nov 16 '18 at 18:54
  • In your edit, you're still doing `document.addEventListener('visibilitychange', this.handleVisibleState, true);`. – Frank Modica Nov 16 '18 at 19:02
  • @Omar My answer essentially does what @PierreDuc's first suggestion does, but my answer creates a separate function so you don't have to change the class method `handleVisibleState` to an arrow function. If you don't care about that, @PierreDuc's change is simpler. – Frank Modica Nov 16 '18 at 19:10
3

Calling removeEventListener() with arguments that do not identify any currently registered EventListener on the EventTarget has no effect. You're passing other function to that.

Using instance arrow function should help in your case:

ngOnInit() {
    document.addEventListener('visibilitychange', this.handleVisibleState, true);
}

ngOnDestroy() {
    document.removeEventListener('visibilitychange', this.handleVisibleState, true);
}

handleVisibleState = () => {
    let vis = document.visibilityState === 'visible';
    this.configsService.update_collab_visible(vis);
}
yurzui
  • 205,937
  • 32
  • 433
  • 399
2

You have to make the function a property field on the class with an arrow function and not use an anonymous function, because the function reference won't be the same.

The reason you are getting the Cannot read property 'update_collab_visible' of undefined error is because you are using a class function instead of a class field. This will move the this context to the function, which is not what you want:

ngOnInit() {
    document.addEventListener('visibilitychange', this.handleVisibleState, true);
}

ngOnDestroy() {
    document.removeEventListener('visibilitychange', this.handleVisibleState, true);
}

handleVisibleState = () => {
    let vis = document.visibilityState === 'visible';
    this.configsService.update_collab_visible(vis);
};

There are also other options. I see you want to use the capture flag for the event. You can think of using rxjs lib as well:

destroy = new Subject<void>();

ngOnInit() {
  fromEvent(document, 'visibilitychange', true).pipe(
    takeUntil(this.destroy)
  ).subscribe((event) => this.handleVisibleState(event));
}

ngOnDestroy() {
  this.destroy.next();
  this.destroy.complete();
}

advertisement

There is also an angular library which adds functionality to the template and component event binding called ng-event-options. If you have installed/imported that, you can simply use:

@HostListener('document:visibilitystate.c')
handleVisibleState() {
    let vis = document.visibilityState === 'visible';
    this.configsService.update_collab_visible(vis);
}

and you're done

Poul Kruijt
  • 69,713
  • 12
  • 145
  • 149
  • the last one will destroy automatically? – Omar Nov 16 '18 at 19:04
  • @Omar yes because it uses the library, and when the component gets destroyed every event with HostListener is removed. It's the same as using an event binding in your template. You also do not need to remove that manually – Poul Kruijt Nov 16 '18 at 19:05
  • I dont understand how the 3rd option's listener is registered? – Omar Nov 16 '18 at 19:13
  • @Omar read about [@HostListener](https://angular.io/api/core/HostListener). It's an angular decorator to bind events on the component. But if you use `document:`, or `window:` or `body:`, it binds to that element. After that comes the event name. The ng-event-options adds the possibility for extra event options like capture (and a bunch more). This is set by single characters. In this case the `c` stands for `capture`, which is what your `true` in your addEventListener does – Poul Kruijt Nov 16 '18 at 19:18
  • I want to stop the working click event on a "stop" button click. And re-start the click event on "Start" button click. – aman jain Jul 22 '19 at 12:21
0

This reason why this.<anything> doesn't work is because the meaning of this is different since it's within a callback and you're not binding it.

If you bind this it should work.

document.addEventListener('visibilitychange', this.handleVisibleState.bind(this), true);

mwilson
  • 12,295
  • 7
  • 55
  • 95
  • 1
    The OP would still need to store the result of `this.handleVisibleState.bind(this)` to pass it to `removeEventListener`. – Frank Modica Nov 16 '18 at 19:08