Troubleshooting: Unable to Trigger Click Events Outside Angular Component

Check out the code I've put together on Stackblitz. Here, you'll find my custom component that allows for toggling between two ng-content elements based on click events.


        import {Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output} from '@angular/core';
        import {fromEvent, Subject} from "rxjs";
        import {untilDestroyed} from "ngx-take-until-destroy";
        import {filter, switchMapTo, take} from "rxjs/operators";
        
        @Component({
            selector: 'app-editable-inplace',
            templateUrl: './editable-inplace.component.html',
            styleUrls: ['./editable-inplace.component.css']
        })
        export class EditableInplaceComponent implements OnInit, OnDestroy {
        
            @Output() update = new EventEmitter();
            mode: 'view' | 'edit' = 'view';
            editMode = new Subject();
            editMode$ = this.editMode.asObservable();
        
            // Remaining code goes here...
    

Template:


        <div *ngIf="mode==='view'" >
            <ng-content select="[view]"></ng-content>
        </div>
        <div *ngIf="mode==='edit'" >
            <ng-content select="[edit]"></ng-content>
        </div>
    

Usage:


        <app-editable-inplace >
            <div view>
                <h3>Click to edit [not working :-( ]</h3>

            </div>
            <div edit>

                        <input placeholder="Click to edit"   >

            </div>
        </app-editable-inplace>
    

However, I'm facing some issues such as the 'clickOutside$' triggering instantly when clicking on the view element. Additionally, the line

this.element.contains(target) === false
doesn't seem to work as expected. Even though the console shows that the host contains the clicked item, it always registers as a click outside (a bit confusing).

https://i.sstatic.net/P6PZR.png

Answer №1

When it comes to events in the browser, many of them follow a process called bubbling, with the click event being one such example. This means that the event travels from the target element upwards. If you have registered an event with the same name higher up in the hierarchy, it will be triggered regardless of what happens in the target handler.

 document  =====================================================>  outsideHandler
  body                                                          /\
    ....                                                        ||  
    <app-editable-inplace>                                   bubbling
      <div view>                                                /\
        <h3>Click to edit [not working :-( ]</h3> <===== target || viewClickHandler
      </div>
      ...
    </app-editable-inplace>

As a result, the clickOutside$ event is triggered immediately.

To address this issue, there are several solutions:

1) The simplest approach is to use event.stopPropagation() to prevent further event bubbling.

private viewModeHandler() {
    fromEvent(this.element, 'click').pipe(
        untilDestroyed(this)
    ).subscribe((e: any) => {
        console.log('clicked inside');
        e.stopPropagation(); <==================== add this

2) Since both handlers involve the same bubbling event, you can set a flag to avoid processing it in the top handler.

 private viewModeHandler() {
    fromEvent(this.element, 'click').pipe(
        untilDestroyed(this)
    ).subscribe((e: any) => {
        console.log('clicked inside');
        e.fromInside = true;


 ...
 const clickOutside$ = fromEvent(document, 'click').pipe(
  filter((e: any) => {
    return !e.fromInside && this.element.contains(e.target) === false
  }),

3) Another option is to capture the click event during the capture phase:

fromEvent(this.element, 'click', { capture: true }).pipe(

... 

const clickOutside$ = fromEvent(document, 'click', { capture: true })

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

Decorators in Angular 9 do not have the capability to support function expressions

Currently, I am working with Angular 9 and facing an issue while generating dynamic routes values during runtime. I have implemented a ComplexUrlRouter to achieve this functionality and integrated it into my Route configuration. However, I encountered the ...

Concerns about unchanging Behavior Subject affecting Ionic 4 interface

I have created a list program using Ionic 4 and attempted to update the list by utilizing BehaviorSubject from rxjs. The list updates properly when initialized in the ngOnInit() method, but fails to update within the add() callback. Despite logging the ou ...

Encountering a 'ng serve' error while trying to include a new SCSS style in an

I recently created a fresh Angular application using the CLI. For my stylesheet, I opted for SCSS. Upon running the application with ng serve, everything was running smoothly. However, when I added some styles to the stylesheet, such as: body { backgr ...

Exploring the functionality of the Aria role attribute within Angular 2

It's strange that the ARIA role attribute doesn't seem to be functioning as expected for me in Angular 2. I attempted to assign a div role of "listbox" and set the children as role "option", but it still doesn't work. Could someone assist m ...

Mongodb's adaptive criteria for email notifications

I am currently exploring methods to store conditions in mongodb for querying and validation purposes, followed by executing actions based on the outcome of the condition check. Let's begin with an illustration of the event object that I am contemplat ...

Dealing with compilation errors in TypeScript

I'm working on a simple TypeScript program that looks like this - const users = [{ name: "Ahmed" }, { name: "Gemma" }, { name: "Jon" }]; // We're trying to find a user named "jon". const jon = users.find(u => u.name === "jon"); However, wh ...

Conflicting TypeScript errors arise from a clash between React version 16.14 and @types/hoist-non-react-statics 3.3.1

Currently in the process of upgrading an older project to React 16.14, as we are not yet prepared for the potential breaking changes that would come with moving up to React 17 or 18. As part of this upgrade, I am also updating redux and react-redux to ver ...

Can you explain the significance of using square brackets in the typescript enum declaration?

While reviewing a typescript file within an Angular ngrx project titled collection.ts, I came across the declaration of enum constants. import { Action } from '@ngrx/store'; import { Book } from '../models/book'; export enum Collecti ...

Scoped variable in Typescript producing a generated Javascript file

I'm currently learning TypeScript through an online course, and I've encountered a problem that seems to be related to a VSCode setting. Whenever I compile app.ts, it generates the app.js file, but I immediately encounter a TypeScript error. It& ...

Creating an Ionic 3 canvas using a provider

I recently followed a tutorial on integrating the canvas API into Ionic, which can be found at this link. However, I encountered an issue where all the canvas-related functions had to be placed within the pages class, making it quite cumbersome as these f ...

What kind of Input field is being provided as an argument to a TypeScript function?

Currently, I am working through an Angular 2 tutorial where an input element is being passed to a function through a click event. The tutorial includes an addTodo function with the following signature: addTodo(event, todoText){ }. However, there is a warn ...

tips on how to shade a column in the material data table according to a specific condition when the table is first loaded

Is there a way to automatically highlight certain rows in the angular material data table based on specific column values when the table loads? Let's take a look at an example component below: @Component({ selector: 'table-basic-example', ...

How to convert the return value of a function into a string in Angular 2

I am hoping to place the returned value within this markup: <h2>{{getSelectedUserName}}</h2> Below is the function I intend to utilize, which will return a string: public getSelectedUserName(): string { let firstName = this.selectedUser. ...

Steps for Properly Defining Next.js getServerSideProps as a Function Declaration

I've been working on implementing getServerSideProps (additional information available here, and detailed API documentation here), but my challenge lies in utilizing it as a function declaration instead of an expression. Despite searching for relevant ...

Decorator used in identifying the superclass in Typescript

I am working with an abstract class that looks like this export abstract class Foo { public f1() { } } and I have two classes that extend the base class export class Boo extends Foo { } export class Moo extends Foo { } Recently, I created a custom ...

The importance of subscribing in service calls: Exploring Angular 2/4

As a newcomer to angular4, I am delving into the world of observables and subscriptions. export class MyComponent implements OnInit { private subscription: Subscription ngOnInit() { // situation-1 -> making service call 1 with subscri ...

Load Order Possibly Disrupted by Arrival of Barrel Imports

When attempting to unit test my component, I keep encountering errors related to my import statements: Error: Cannot resolve all parameters for 'MyComponent'(undefined, FormBuilder). TypeError: Cannot read property 'toString' of undef ...

"Unlocking the potential: Exploring the power of keyof with

Currently, I am working with React Redux toolkit and keyof to specify that one element of my action payload should be the key of the type that my state is composed of. This allows me to update the properties of the state using a redux action. However, I am ...

Exploring Angular observables: Tips for gathering specific fields from a nested array within an object into a new array

I am working on parsing a JSON object and extracting specific fields to create their own array. My goal is to loop through the provided object and gather the 'appSupportedId' values into a separate array. An issue has arisen, leading to an ERRO ...

Angular Material: Enhanced search input with a universal clear button

After searching for a cross-browser search control with a clear button similar to HTML5, I found the solution rendered by Chrome: <input type="search> The code that gave me the most relevant results can be found here. I used the standard sample w ...