Troubleshooting problem with Angular Click Outside Directive and unexpected extra click event issue

The challenge I'm facing involves implementing a custom Click Outside Directive for closing modal dialogs, notifications, popovers, and other 'popups' triggered by various actions. One specific issue is that when using the directive with popups opened via mouseover event, it requires two clicks to close, whereas popups opened by clicking a button only need one click to dismiss.

For reference, here is an example on Plunker demonstrating this issue.

Below is the code snippet for the directive:

@Directive({
  selector: '[clickOutside]'
})
export class ClickOutsideDirective {
  @Output('clickOutside') clickOutsideEvent = new EventEmitter();

  private globalClick: Subscription;

  constructor(private elRef: ElementRef) { }

  ngOnInit() {
    this.globalClick = Observable
      .fromEvent(document, 'click')
      .skip(1)
      .subscribe((event: MouseEvent) => {
        this.clicked(event);
      });
  }

  ngOnDestroy() {
    this.globalClick.unsubscribe();
  }

  private clicked(event: MouseEvent) {
    let clickedInside = this.elRef.nativeElement.contains(event.target);

    if (!clickedInside) {
      this.clickOutsideEvent.emit();
    }
  }
}

In the ngOnInit() method, an observable is set up to detect clicks on the document and trigger the clicked() function to validate if the click was outside the element. It uses a skip operator to filter out the first event which is not needed within the directive. However, this workaround causes the requirement of two clicks to close a popup generated through methods other than a direct click (as seen in the Mouse Over example on Plunker).

I am looking for a more elegant, simplified solution to address this problem without resorting to 'hacky' methods like stopping event propagation or introducing additional flags. Any suggestions would be greatly appreciated!

Answer №1

Why not consolidate the logic within the popup component itself instead of having it in a separate directive? After all, the popup component is responsible for handling the closing functionality.

To achieve this, modify the component to include a fixed position div with a click listener that sits behind the popup. Whenever this background div is clicked, trigger the clickOutside event.

import {Component, Output, EventEmitter} from '@angular/core';

@Component({
  selector: 'popup',
  styles: [`
      .background {
    z-index: 1;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;

      }

      .modal-content {
    left: 0;
    right: 0;
    z-index: 3;
      }
  `],
  template: `
    <div class="background" (click)="close()">
      <div class="modal-content">
    Click outside to close me!
      </div>
    </div>
  `,
})
export class PopupComponent {

  @Output()
  clickOutside = new EventEmitter();

  close() {
    this.clickOutside.emit();
  }
}

You can view a working example on Plunker here: https://plnkr.co/edit/9QBJ0PXOg2GAUpBacgQV?p=preview

Answer №2

After some research, I have finally come across a solution to this issue. It may not be as elegant as anticipated, but essentially, I have implemented a timer observable with a zero value that triggers the 'click' event observable on the document. This approach has been tested in various modern browsers, including iPad and Android Chrome, and appears to function correctly across all platforms.

ngOnInit() {
   this.globalClick = Observable
      .timer(0)
      .switchMap(() => {
          return Observable.fromEvent(this.document, 'click');
      })
      .subscribe((event: MouseEvent) => {
        this.clicked(event);
      });
}

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

Is it possible to use an angular expression as the ng-click function?

I have been searching everywhere for the answer to this question. Within my code, there is an angular object called column.addOnParams. This object includes a child element named rowClick, which can be accessed using column.addOnParams.rowClick. The val ...

Encountered an error while trying to create a new NestJS project using yarn: Command execution failure - yarn install --

Having trouble installing a new NestJs project using yarn. Operating system : Microsoft Windows [version 10.0.19044.3086] Node version : 18.17.1 npm version : 9.6.7 yarn version : 1.22.19 Nest cli version : 10.1.16 I attempted to install by running : npm ...

When it comes to utilizing jQuery for the mobile view, how many jQuery libraries are recommended for optimal performance?

I'm currently constructing a website using ROR, and for the mobile view, I've implemented the mobile_fu gem. The designer provided me with a design for the mobile view that includes various jQuery sliders, players, image sliders, drop-down menus ...

Retrieving Values from Components in Angular 6

Encountering an issue while retrieving values from different components. The scenario involves 2 components - ReceiveBookingManageComponent as the first component and DriverTablePickerComponent as the second one. The problem arises in DriverTablePickerCo ...

The CanLoad guard does not permit access to the root route

My original idea was to create a project where the main website, located at "/", would only be loaded lazily after the user logs in. const routes: Routes = [ { path: '', canLoad: [AuthGuard], loadChildren: './home/home.module#HomeModule&a ...

Node: How can I retrieve the value of a JSON key that contains a filename with a dot in

I have a JSON file named myjson.json, with the following structure: { "main.css": "main-4zgjrhtr.css", "main.js": "main-76gfhdgsj.js" "normalkey" : "somevalue" } The purpose is to link revision builds to their original filenames. Now I need to acce ...

Blank area located at the bottom of the document

I'm having trouble designing a webpage without a scroll bar because there isn't much content to display on the page. I've tried searching for solutions and following steps to fix the issue, but I haven't had any success. If anyone can a ...

Obtaining a result from a jQuery Ajax call

I am exploring the use of JavaScript in an object-oriented style, and have a method that requires making a remote call to retrieve data for a webpage to function properly. To achieve this, I have created a JavaScript class to handle data retrieval in order ...

Attempting to replicate the action of pressing a button using Greasemonkey

I am currently working on a greasemonkey script to automate inventory updates for a group of items directly in the browser. I have successfully implemented autofill for the necessary forms, but I am facing challenges with simulating a click on the submissi ...

Elevate state in React to modify classNames of child elements

I have a structured set of data divided into 6 columns representing each level of the hierarchy. When a user makes selections, their chosen location is highlighted with a dynamic CSS class name and displays the relevant data list. I've managed to impl ...

The object may be null even after being enclosed in an if statement

In my Vue component, I have implemented the following method: dataURLtoBlob(dataurl: string): Blob { const arr: string[] = dataurl.split(","); if (arr) { if (arr[0]) { const mime = arr[0].match(/:(.*?);/)[1]; ...

The Angular JS field will only be considered valid if its value is more than zero

I have a text box in my form that changes its value dynamically when a user selects a category from a dropdown menu. Initially, the value is set to 0. $scope.validCats = "0"; HTML: <input ng-model="validCats" value="" type="text" class="form-control ...

Exploring the syntax of Vue's data function

I encountered a syntax error while compiling my assets: Syntax Error: SyntaxError: C:\xampp\htdocs\dtcburger.com\resources\js\components\stripe\STRIPEform3.vue: Unexpected token, expected ";" (51:12) 49 | { ...

How can I update the background color of the specified element when the text changes without triggering a postback?

Seeking guidance on JavaScript as I am not very adept in it. We have a webpage where users upload .XLS/.CSV files and review the data before submitting it to our database. Users can edit cells in the uploaded document on our "review" screen before final su ...

Another option instead of using $index for displaying serial numbers in ng-repeat

Looking to display a serial number for each table data entry being generated through the use of ng-repeat. The current code I have is as follows: <tr ng-repeat="usageRecord in applicationUsageDataForReport"> <td style="text-align: center">&l ...

Warning: Attempting to destructure the property 'name' from 'req.body', which is undefined, is causing a TypeError

Currently, I am diving into the world of MERN Stack web development and running into a unique issue. When using Postmate to input data from the body to the database, everything works smoothly when done from the server.js file. However, when attempting the ...

Conceal an element from view upon clicking on it

Task at Hand : Implement a smooth element hiding effect upon clicking, similar to the behavior on this website , where the letter A fades away smoothly when clicked. Is it possible to achieve this effect using only HTML, CSS, and JavaScript? var t ...

Generating fresh objects to establish a Many-to-Many connection with Sequelize

I am currently utilizing NodeJS, Express, Sequelize, and PostgreSQL in my project. Within my application, I have defined two models: a "User" model and a "Social" model. My goal is to establish a many-to-many relationship where a user can be associated wi ...

What steps can I take to ensure that my Onetrust consent script is properly loaded before implementing Facebook pixel tracking in NextJS?

Looking for a solution to load my Cookies consent script as the first script in our NextJS application. The problem arises because we have Facebook pixel tracking implemented and they use parentNode.insertBefore which places their script on top. This is h ...

Having trouble with Angular router.navigate not functioning properly with route guard while already being on a component?

I am currently troubleshooting an issue with the router.navigate(['']) code that is not redirecting the user to the login component as expected. Instead of navigating to the login component, I find myself stuck on the home component. Upon adding ...