Tips for resolving the issue of dropdown menus not closing when clicking outside of them

I am currently working on an angular 5 project where the homepage consists of several components. One of the components, navbarComponent, includes a dropdown list feature. I want this dropdown list to automatically close when clicked outside of it.

Here is the code snippet:

ngOnInit() {
    this.showMenu = false;
}

toggle() {
    this.showMenu = !this.showMenu;
}

<div *ngIf="isConnect" class=" userStyle dropdown-toggle " (click)="toggle()">
    <ul class="dropdown-menu subMenu" role="menu" *ngIf="showMenu">
        <li (click)="profile()" class="subMenuItem"> PROFILE</li>
        <li (click)="administration()" class="subMenuItem subMenuItem-last">ADMINISTRATION</li>
        <li class="subMenuItem subMenuItem-last"><button class="btn blue-btn" (click)="logout()" ><mat-icon mat-list-icon fontIcon="icon-logout"></mat-icon>LOGOUT</button></li>
    </ul>
</div>

Answer №1

Incorporating this functionality into my project, the first step is to attach a click event listener to the window within the ngOnInit lifecycle hook.

ngOnInit() : void {
  this.windowClickSubscription = Observable
    .fromEvent(window, "click")
    .subscribe(this.handleWindowClick)
}

Whenever a click occurs on the window, the this.handleWindowClick method will be invoked. Let's proceed with implementing this method:

handleWindowClick(res: any) {
  let target: any = res.target;
  let threshold: number = 0;
  while(target && target.className != 'grouped-control' && threshold <= 4) {
    target = target.parentElement;
    threshold++;
  }
  if(target && target.className != 'grouped-control') this.hasOptions = false;
}

This function is designed to search for the parent element of the click target until it reaches the specified element ('grouped-control'). If found, no action is taken; otherwise, the control is closed by toggling the hasOptions flag.

To properly clean up, remember to unbind the event in the ngOnDestroy hook:

ngOnDestroy(): void {
  this.windowClickSubscription && this.windowClickSubscription.unsubscribe();
}

Lastly, don't forget to declare the this.windowClickSubscription property and bind the component reference to the handleWindowClick function in your constructor.

Edit

Add the following line to your constructor to bind the function reference:

constructor() {
  this.handleWindowClick = this.handleWindowClick.bind(this);
}

This step ensures that the function can be used as a callback handler with the correct component reference. Additionally, I am utilizing the *ngIf directive to toggle the visibility of the control based on the value of this.hasOptions.

Answer №2

When the Dropdown is opened, a class 'open' is added to the element with the class 'dropdown-toggle'. The class is removed when the dropdown is closed. Additionally, clicking outside of the dropdown area will also close it.

Below is the code I used to achieve this functionality:

<div class="drop-menu">
    <a class="dropdown-toggle" title="Filter" (click)="openDropdown()">
       <span class="fa fa-arrow"></span>
    </a>
    <ul class="dropdown-menu subMenu" role="menu" *ngIf="showMenu">
       <li (click)="profile()" class="subMenuItem"> PROFILE</li>
       <li (click)="administration()" class="subMenuItem subMenuItem-last">ADMINISTRATION</li>
       <li class="subMenuItem subMenuItem-last"><button class="btn blue-btn" (click)="logout()" ><mat-icon mat-list-icon fontIcon="icon-logout"></mat-icon>LOGOUT</button></li>
    </ul>
</div>

Snippet from component.ts file:

constructor(private renderer: Renderer2) { }
ngOnInit() {
  const selectDOM = document.getElementsByClassName('dropdown-toggle')[0];
  this.renderer.listen('document', 'click', (evt) => {
    const eventPath = evt.path;
    const hasClass = _.where(eventPath, { className: 'drop-menu' });
    if (hasClass.length <= 0) {
      this.renderer.removeClass(selectDOM, 'open');
    }
  });
}

openDropdown() {
  const selectDOM = document.getElementsByClassName('dropdown-toggle')[0];
  if (selectDOM.classList.contains('open')) {
    this.renderer.removeClass(selectDOM, 'open');
  } else {
    this.renderer.addClass(selectDOM, 'open');
  }
}

Answer №3

Include a TemplateRef-Id in your navigation menu:

<ul #menuRef class="dropdown-menu subMenu" role="menu" *ngIf="showMenu">

                ....
</ul>

Retrieve the TemplateRef within your code:

@ViewChild('menuRef') menuRef: TemplateRef<any>;

Next, set up a global click event listener at the document level:

@HostListener('document:click', ['$event'])
hideMenu(event) {
  if (!this.menuRef.nativeElement.Contains(event.target) {
    if (this.showMenu) {
       this.showMenu = false;
    }
  }
}

If the click occurs outside of the dropdown area, hide the menu by setting showMenu=false.

Consider utilizing a component for your dropdown instead. Check out ng-select, which handles these functionalities automatically.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Stop the Text Table from being highlighted

My webpage includes a dynamic table where users can select multiple rows. jQuery events and CSS are utilized to provide visual feedback when a row is selected. However, pressing the shift key sometimes causes text to become highlighted, which is not idea ...

concealed highcharts data labels

I attempted to create a bar chart using Highcharts, and initially it worked fine. However, I encountered an issue when displaying multiple indicators - the datalabels for certain data points are hidden. For example, the datalabel for "provinsi aceh" is not ...

Using TypeScript, apply an event to every element within an array of elements through iteration

I have written the code snippet below, however I am encountering an issue where every element alerts the index of the last iteration. For instance, if there are 24 items in the elements array, each element will alert "Changed row 23" on change. I underst ...

How can I use JavaScript or CSS to identify the specific line on which certain words appear in the client's browser?

Imagine I have the following HTML structure: <div> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco labor ...

Using AngularJS controller to implement filtering functionality

I am a beginner at using Angular and have successfully implemented a filter to translate text for localization in my HTML view: <input type="button" class="btn btn-link" value="{{'weeklyOrdersPage.reposting' | translate}}" ng-click="sortBy(&a ...

Is it possible to adjust the color of the iOS status bar using NativeScript, Angular 2, and TypeScript?

I recently came across this npm package called NativeScript Status Bar, available at the following link: https://www.npmjs.com/package/nativescript-statusbar However, I'm facing an issue because I cannot use a Page element as I am working with Angul ...

Effortlessly move and rearrange various rows within an HTML table by utilizing JQuery alongside the <thead> elements

My JsFiddle has two tables and I want to switch rows <tr> between them using Jquery. However, due to the presence of the <thead> tag, it is not functioning properly. I suspect that the issue lies with the items in the selector, but I am unsure ...

Save message in the callback function of the express app.listen method

I'm currently integrating winston logging into my application and aiming to switch all info or error level logs with winston's .info and .error. Everything seems to be working well except when trying to log an info message from within the app.lis ...

Expansive image coverage

My footer design includes a rounded left image, a repeating middle section, and a rounded right image. How can I ensure that it scales perfectly responsively without overlapping? Ideally, the solution would involve adjusting the width of the middle section ...

Learn to save Canvas graphics as an image file with the powerful combination of fabric.js and React

I am currently utilizing fabric.js in a React application. I encountered an issue while attempting to export the entire canvas as an image, outlined below: The canvas resets after clicking the export button. When zoomed or panned, I am unable to export co ...

Is there a way to extract data from the Redux state in a React component?

Seeking assistance with making an API request from Redux, followed by saving the data in my React state (arrdata). Although the API call is successful, I am facing challenges updating the App.js state based on the Redux API call. Any suggestions or insig ...

When running the `vue-cli-service test:unit` command, an error involving an "Unexpected token" message related to the usage of the spread operator

Within my code, I am utilizing the destructuring operator. However, during the module build phase, I encountered an "Unexpected token" error. Any suggestions on how to resolve this issue without completely rewriting my code to avoid using the destructuring ...

Creating a dynamic JSON structure from an array list of elements: A step-by-step guide

I am faced with the task of transforming an array of employee IDs, listed as follows: empIds: [38670, 38671, 38672, 38673], into a JSON payload structured like this: { "members": [ { "EmployeeId": &quo ...

Use $parse to extract the field names that include the dot character

Suppose I have an object with a field that contains a dot character, and I want to parse it using $parse. For instance, the following code currently logs undefined - var getter = $parse('IhaveDot.here'); var context = {"IhaveDot.here": 'Th ...

Eliminate all blank spaces at the top and bottom of Span by squishing it

Imagine I have this code snippet: <span class="labels" style="background-color: #FFFFFF;border: 1px solid #000000;font-family: Verdana;font-size: 11px; ">74.58 ft.</span> I am looking for a way to compress the span element so that only the te ...

Developing a Node.js and Express REST API for generating tailored routes for custom fields

I'm currently using node.js and express framework to build my REST API server. One of the features I want to implement is similar to the Facebook graph API, where I can pass specific fields in my API routes like: /me?fields=address,birthday,email,do ...

Implementing the onClick function for the correct functionality in a ReactJS map component

I have designed a mockup and now I am trying to bring it to life by creating a real component. View my component mockup here Starting with something simple, I created 3 buttons and an array. However, when I click on any button, all features are displayed ...

Exploring the differences between two CSS style attributes using JQuery

Can someone help me with a quick question about CSS? I need to compare two style attributes, specifically margin-left. If the margin-left is less than 500px, I don't want to make any changes. However, if it's greater than 500px, I want to add ano ...

Mastering the Art of Adding Headers in Angular

Here is the Angular service code: getCareer(dataout: any){ let headers = new HttpHeaders().append('Content-Type', 'application/json').append('Authorization','Bearer'+' '+GlobalService.authToken); //hea ...

A guide on transferring a Vue component to a custom local library

After successfully creating components using template syntax (*vue files), I decided to move common components to a library. The component from the library (common/src/component/VButton): <template> <button ... </button> </templat ...