Angular Dropdown Menu Close On Click Outside
In this post I want to show you how to create click outside directive inside Angular. And if you are wondering why do you need such directive this is because every single time when we want to close a modal, tooltip or dropdown menu we must implement click outside.
This is why let's create together reusable click outside directive for your next project.
Creating dropdown component
I already generated an empty Angular project. And I want to implement click outside directive with a real usage. This is why we will create a dropdown component with custom click outside directive.
Let's first add markup for our dropdown.
<!-- app.component.html -->
<div class="menu-container">
<div class="menu-toggler">Toggle menu</div>
<div class="menu">
<div class="menu-item">News</div>
<div class="menu-item">Contacts</div>
<div class="menu-item">About</div>
</div>
</div>
We added markup with container, button to toggle our menu and menu itself which will be shown.
Now let's add some styles.
# src/styles.css
.menu-container {
width: 200px;
background: #04aa6d;
font-size: 20px;
}
.menu-toggler {
padding: 10px;
}
.menu {
background: #333;
color: #fff;
padding: 10px;
}
.menu-item {
cursor: pointer;
padding: 5px 0;
}
.menu-item:hover {
text-decoration: underline;
}
Here is how it looks in browser.
Now let's add some basic behaviour to close dropdown on click.
<div class="menu-container">
<div class="menu-toggler" (click)="toggleMenu()">Toggle menu</div>
<div class="menu" *ngIf="isMenuOpened">
<div class="menu-item">News</div>
<div class="menu-item">Contacts</div>
<div class="menu-item">About</div>
</div>
</div>
export class AppComponent {
isMenuOpened: boolean = false;
toggleMenu(): void {
this.isMenuOpened = !this.isMenuOpened;
}
}
We successfully implemented our click behaviour in our dropdown. But we are still missing click outside feature which is exactly the point of this video.
It's time to create our click outside directive.
// src/app/clickOutside.ts
@Directive({
selector: '[clickOutside]',
})
export class ClickOutsideDirective {
@Output() clickOutside = new EventEmitter<void>();
constructor(
private element: ElementRef,
@Inject(DOCUMENT) private document: Document
) {}
}
Here we created out directive which has an output which will be fired when we are clicking outside. In our constructor
we injected element
and document
references to get access to direct DOM elements of them later.
// src/app/clickOutside.ts
export class ClickOutsideDirective implements AfterViewInit, OnDestroy {
@Output() clickOutside = new EventEmitter<void>();
documentClickSubscription: Subscription | undefined;
constructor(
private element: ElementRef,
@Inject(DOCUMENT) private document: Document
) {}
ngAfterViewInit(): void {
this.documentClickSubscription = fromEvent(this.document, 'click')
.pipe(
filter((event) => {
return !this.isInside(event.target as HTMLElement);
})
)
.subscribe(() => {
this.clickOutside.emit();
});
}
ngOnDestroy(): void {
this.documentClickSubscription?.unsubscribe();
}
isInside(elementToCheck: HTMLElement): boolean {
return (
elementToCheck === this.element.nativeElement ||
this.element.nativeElement.contains(elementToCheck)
);
}
}
Here we did a lot of stuff.
- We used
ngAfterViewInit
we wait until our DOM element will be rendered in DOM - We created a stream with
fromEvent
which we bind todocument
. It will be triggered every single time when we click on document - We filter our clicking with
isInside
function where we check if we clicked inside current element or not - If we clicked outside we emit out output to notify our parent about clicking outside
Important moment here is to unsubscribe from out stream when our element is destroyed. In other case we will have a subscription which will hang there forever.
Now it's time to register our directive.
@NgModule({
declarations: [AppComponent, ClickOutsideDirective],
...
})
export class AppModule {}
And we can use it now inside our dropdown component.
<div class="menu-container clickOutside (clickOutside)="clickedOutside()">
<div class="menu-toggler" (click)="toggleMenu()">Toggle menu</div>
<div class="menu" *ngIf="isMenuOpened">
<div class="menu-item">News</div>
<div class="menu-item">Contacts</div>
<div class="menu-item">About</div>
</div>
</div>
Here we used clickOutside
directive and created clickOutside
event which should close our dropdown.
export class AppComponent {
isMenuOpened: boolean = false;
toggleMenu(): void {
this.isMenuOpened = !this.isMenuOpened;
}
clickedOutside(): void {
this.isMenuOpened = false;
}
}
As you can see in browser, we can close our dropdown now with click outside. And this directive is fully reusable in any project.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done