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

Identify the row containing a value of null using jQuery (functionality not performing as anticipated)

Whenever the user clicks on the GetData button, I retrieve JSON data and display it in an HTML table similar to the demo below. Demo: https://plnkr.co/edit/I4XYY6CZohf7IS6wP8dR?p=preview There are instances where the value can be null, such as the loanNu ...

Tips for maintaining the state in a React class component for the UI while navigating or refreshing the page

Is there a way to persist the selection stored in state even after page navigation? I have heard that using local storage is a possible solution, which is my preferred method. However, I have only found resources for implementing this in functional compone ...

When making xmlhttp requests, IE9 will prioritize loading from the cache rather than from the server when the data is available

During my local development process, I've been utilizing this AJAX code: function getChart(num,ld,margin,idr) { idr = typeof(idr) != 'undefined' ? idr : 0; $(ld).style.display="inline-block"; if (window.XMLHttpRequest) { ...

Change the text field's border color if the field is not empty

On my website, there is a TextField where users can enter values to perform a site search. My question pertains to the border color of this field. Normally, the border color is black when the field is not in use. However, when a user clicks on the field an ...

retrieving form data from a submit button using objects in PHP

I am using objects to fetch a form (including a submit button) from another page. However, I am struggling to extract the POSTED information from that submit button and believe that AJAX might be necessary. Here is an example: Page 1 initiates a call to ...

find the middle element in the Vue array

Currently in the process of developing a custom Vue carousel component, I have utilized some code snippets from this resource: link My goal right now is to enhance the slider with additional navigation bullets. However, due to the justify-content:center p ...

Encountering an error in resolving symbol values statically within the Angular module

Following a helpful guide, I have created the module below: @NgModule({ // ... }) export class MatchMediaModule { private static forRootHasAlreadyBeenCalled: boolean = false; // This method ensures that the providers of the feature module ar ...

Utilize user input to fetch data from an external API

Let's say there is a field for 'part number' input that is not enclosed in a form tag. Whenever a user enters a value, the onblur event or a button positioned next to the input field should trigger a query to an external site via its API and ...

What steps are required to generate dist/app.js from a script file in my TypeScript project?

I am currently working on a project using node, express, and TypeScript. When I run npm run build, everything builds without any issues. However, when I attempt to run npm run start, I encounter the following error: @ruler-mobility/[email protected] /User ...

What causes the form to consistently show as invalid once it has been submitted?

register.html : <form [formGroup]="signupForm" (submit)="onSubmit()" class="form-detail"> <h2>Registration Form</h2> <div class="form-row-total"> <div class="form-row"> <in ...

Angular 9's reactive form feature seems to ignore the need for form validation when a dropdown is populated

Currently, I am in the process of developing a reactive form for capturing user details. The form includes two dropdowns for selecting the province and municipality. Upon initialization of the form, the province field is populated using a *ngFor loop, and ...

Guide to placing a button on the same line as text with the use of JavaScript

Does anyone know how to add a button to the right of text that was added using JS DOM? I've tried multiple techniques but can't seem to get it right - the button always ends up on the next line. Any tips on how to achieve this? var text = docu ...

The input value does not update in the controller when using AngularJS ng-model

Why is it that when I print out console.log($scope.inputvalue), the variable does not update with the values I enter in the input field? Did I misunderstand the purpose of ng-model? If so, how can I pass a value from the view to the controller? (functi ...

Encountering issues while trying to execute npm and node commands within Visual Studio Code

When I attempt to execute node commands in a command window, such as ng serve -o, everything works fine. However, when I try to do the same in VS Code, I encounter the following error message: ng : The term 'ng' is not recognized as the name of ...

Discovering the closest date among the elements in an array

Currently, I am facing an issue while trying to identify the highest date within an array of objects which have varying dates assigned to each one. The code seems to be functioning well when the dates are above January 1st, 1970, however, any date prior to ...

Having difficulty modifying the styling of a paragraph within a div container

I have been working on a function that is supposed to adjust the font-size and text-align properties of a paragraph located within a div tag once a button is pressed. function customizeText() { document.getElementById('centretext').innerHTML = ...

Unable to execute multiple instances of Selenium PhantomJS concurrently

I have encountered an issue while using Selenium's node.js API to run PhantomJS instances against a series of web pages. The code that I have written to perform actions on the pages is functioning correctly, but it appears that only one instance of Se ...

Exploring numerical elements in interactive content

Struggling with the Wikipedia API and encountering issues with the results that are returned. {"query":{ "pages":{ "48636":{ "pageid":48636, Concerned about how to access a specific ID (such as 48636) without knowing it in advance ...

What is the best way to control the amount of rows displayed in my gallery at any given time?

I need help with customizing my gallery that is dynamically generated from a directory using PHP. My goal is to display only 2 rows of 4 images each, totaling 8 images, with a "show more" button for loading additional rows. How can I set a limit on the n ...

Issue detected with XMLHttpRequest - "The requested resource does not have the 'Access-Control-Allow-Origin' header."

Currently, I am working on the frontend development of an application using Angular 2. My focus is on loading an image from a third-party site via XMLHttpRequest. The code I have implemented for this task is as follows: loadFile(fileUrl) { const ...