2

I have a directive that opens a sub menu on click, but I would like to improve it by only activate document click when the target element has been clicked on. So the question is to improve this directive or how to dynamically add host listener.

import { Directive, HostListener, Input, AfterContentInit, ElementRef, ViewContainerRef, ContentChild } from '@angular/core';

@Directive({
    selector: '[appSubMenu]'
})

export class SubMenuDirective implements AfterContentInit {
    private visible: boolean = false;
    @ContentChild('subMenu') child: ElementRef;

    constructor(private vcRef: ViewContainerRef) { }

    ngAfterContentInit() {
        this.child.nativeElement.style.display = 'none';
    }

    @HostListener('document:click', ['$event', '$event.target'])
    show(event:MouseEvent, targetElem: HTMLElement): void {
        if (this.vcRef.element.nativeElement === targetElem && !this.visible) {
            this.child.nativeElement.style.display = 'block';
            this.visible = true;
        } else {
            this.child.nativeElement.style.display = 'none';
            this.visible = false;
        }
    }
}
taropoo
  • 110
  • 2
  • 8

1 Answers1

8

You can use the Renderer Class to achieve dynamic event bindings. There exists two functions:

  • listen: To listen to events from an element
  • listenGlobal: To listen to global events e.g. document, body

So in your case it would look like the following:

import { Directive, HostListener, Input, AfterContentInit, ElementRef, ViewContainerRef, ContentChild } from '@angular/core';

@Directive({
    selector: '[appSubMenu]'
})

export class SubMenuDirective implements AfterContentInit {
    private visible: boolean = false;
    private unregister: Function;
    @ContentChild('subMenu') child: ElementRef;

    constructor(private vcRef: ViewContainerRef, private renderer: Renderer) { }

    ngAfterContentInit() {
        this.child.nativeElement.style.display = 'none';
    }


    @HostListener('click', ['$event'])
    elementClicked(event: MouseEvent) {
        this.visible = !this.visible;
        if (this.visible) {
            this.unregister = this.renderer.listenGlobal('document', 'click', () => {
                this.child.nativeElement.style.display = 'none';
                this.unregister();
            });
        } else {
            this.child.nativeElement.style.display = 'block';
        }
    }
}
ladi
  • 1,518
  • 2
  • 13
  • 19
  • Renderer class is deprecated from Angular 4+. Here is the work aroud. Link : https://stackoverflow.com/questions/35080387/dynamically-add-event-listener-in-angular-2 – harsh hundiwala Jul 25 '18 at 14:39