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.

Dropdown markup

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.

  1. We used ngAfterViewInit we wait until our DOM element will be rendered in DOM
  2. We created a stream with fromEvent which we bind to document. It will be triggered every single time when we click on document
  3. We filter our clicking with isInside function where we check if we clicked inside current element or not
  4. 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
Did you like my post? Share it with friends!
Don't miss a thing!
Follow me on Youtube, Twitter or Instagram.
Oleksandr Kocherhin
Oleksandr Kocherhin is a full-stack developer with a passion for learning and sharing knowledge on Monsterlessons Academy and on his YouTube channel. With around 15 years of programming experience and nearly 9 years of teaching, he has a deep understanding of both disciplines. He believes in learning by doing, a philosophy that is reflected in every course he teaches. He loves exploring new web and mobile technologies, and his courses are designed to give students an edge in the fast-moving tech industry.