circuit

Create an Angular Directive to Detect Clicking Outside an Object

How to Create an Angular Directive to Detect Clicking Outside an Object




I constantly find the need for a way of detecting when there is a click outside an element. Whether it be a dropdown, a modal, or an input, knowing when the user is trying to click away from something can be really important. Because it’s so common, I’ve standardized the way I handle it into a directive, and usually, just copy-paste this directive into every new project.

The Problem

The most common use case I find for this directive is in creating custom dropdown menus. In Angular, it’s easy to capture click events with the (click) event binding, so opening the dropdown is easy. The problem is when to close the menu. There is no simple event binding that captures a click event outside of the element.

In the past, I’ve tried doing it all with native HTML and CSS by using a hidden checkbox to style the open and closed state. While this eliminates the need for JavaScript, it becomes a nightmare to style correctly for anything but the most basic of dropdowns. In addition, that method still doesn’t close the menu if the user clicks somewhere besides the menu itself.

The Solution

The requirements for a viable solution are pretty straightforward: we want a directive that we can attach to an element, and that easily detects a click event anywhere outside of the element (or its children elements). Here is the code for the directive as well as some further explanation:

@Directive({
    selector: '[clickOutside]'
})
export class ClickOutsideDirective {

    constructor(private elementRef: ElementRef) { }

    @Output() clickOutside = new EventEmitter<MouseEvent>();

    @HostListener('document:click', ['$event', '$event.target'])
    public onClick(event: MouseEvent, targetElement: HTMLElement): void {
        if (!targetElement) {
            return;
        }
        const clickedInside = this.elementRef.nativeElement.contains(targetElement);
        if (!clickedInside) {
            this.clickOutside.emit(event);
        }
    }
}

This directive has an output to emit an event when it detects a click outside of the attached element. By using the HostListener on document clicks, it calls the onClick function. The function simply checks if the clicked target is contained within the attached element’s children. If not, it emits the event. That’s really all there is to it. Using this directive could look something like this:

<div class="dropdown" (click)="dropdownOpen = !dropdownOpen" (clickOutside)="dropdownOpen = false">
...
</div>

Warning: Using this directive too much can cause slowness. Since each click, no matter where it is on the page, calls the onClick function, the function calls can start to add up if the directive appears on very many elements. In addition, modifying the onClick function to be more complicated, can amplify that effect.

I hope you find this directive useful. Thanks for reading.




Continue Learning