Javascript removeEventListener OnDestroy not working in Angular 6

Viewed 2386

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

4 Answers

Store the callback:

private visibilityChangeCallback: () => void;

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

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

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

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);
}

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);

Related