3
votes

I am attempting to bind and unbind a HostListener click event based on a component's input variable.

The component is a popup which has a single input called show. This input holds the active/inactive state of the popup. I would like to fire a click event on the entire document but only when the input show is set to true.

Is there a way to bind and unbind the hostlistener event inside the ngOnChanges function I have?

The current code I have:

import {Component, OnInit, Input, ViewEncapsulation, HostListener} from '@angular/core';

@Component({
    selector: 'custom-popup',
    templateUrl: './popup.component.html',
    styleUrls: ['./popup.component.scss'],
    encapsulation: ViewEncapsulation.None
})
export class PopupComponent implements OnInit {

    @Input() show: boolean = false;

    constructor() {
    }

    ngOnInit() {
    }


    @HostListener('document:click', ['$event.target'])
    clickHandler(targetElement) {
        // Custom code to handle click event
    }

    ngOnChanges(show: boolean) {
        if (this.show) {
            // Bind Click Event
        }
        else {
            // Unbind Click Event
        }
    }
}

Any assistance and insight would be greatly appreciated.

2
Is there a reason you want to unbind the event and not just set a flag that determines whether to skip the rest of the handler or not? It probably wouldn't be much of a performance difference either way (AFAIK). - GregL
I tried that approach already but unfortunately this raises a problem. When I toggle the popup show with a click event on a separate button the HostListener click event fires. The event fires after the show input has changed not before. - Carlton
Have you tried using a separate flag to show then? Set and clear that flag in the ngOnChanges() handler, and hopefully that means the order you want things to happen in will be enforced. - GregL
Unfortunately this method has huge performance drops. i.e. a page has 20+ popup components with hostlistener events attached. - Carlton

2 Answers

2
votes

To ensure that only 1 host listener is set up when a popup is shown, introduce a new child component PopupBody that has the host listener on it, and an @Output() to pass the event back up to the Popup component.

Then, in your template, make the PopupBody component conditional using *ngIf="show" and it should then only bind the host listener when the popup is shown, and unbind it when the popup gets hidden.

-1
votes

In the end I implemented a solution using a service. The service handles the click events on the document and uses an observable to enable components to subscribe to the event.

The popup component subscribes to the stateService click observable when it is opened and unsubscribes when it is closed.

Popup Component:

import {Component, OnInit, Input, ViewEncapsulation, HostListener} from '@angular/core';
import {StateService} from "../services/state.service";
import {Subscription} from "rxjs";  

@Component({
    selector: 'custom-popup',
    templateUrl: './popup.component.html',
    styleUrls: ['./popup.component.scss'],
    encapsulation: ViewEncapsulation.None
})
export class PopupComponent implements OnInit {

    @Input() show: boolean = false;

    constructor(private stateService: StateService) {
    }

    ngOnInit() {
    }

    ngOnChanges(show: boolean) {

        if (this.show) {

            // Timeout to ensure subscription after initial click
            setTimeout(() => {

                // Subscribe to the document click service when the input opens
                if (!this.clickSubscription || this.clickSubscription.closed) {
                    this.clickSubscription = this.stateService.documentClick$.subscribe(targetElement => {
                        // Logic for closing the popup
                    });
                }
            });
        }
        else {

            // Remove the subscription when the input closes
            if (this.clickSubscription && !this.clickSubscription.closed) {
                this.clickSubscription.unsubscribe();
            }
        }
    }
}

@GregL Thank you for your suggestions