如何在Angular 7中检测元素外部的单击?

Lul*_*ali 5 html angular

div将在页面作为东面板上被动态地示出的,当open panel按钮被点击。bool showEastPanel变量是用于打开和关闭东部面板的变量。我试图用来(clickoutside)关闭面板(设置showEastPanel为false),但是打开的面板首先在Angular挂钩上运行,并且面板设置为true,然后设置为false,并且面板不显示。有什么办法使“ clickoutside”范围不包括按钮?

<div [ngClass]="{'d-none': !showEastPanel, 'east-panel-container': showEastPanel}" (clickOutside)="ClosePanel()">
<div id="east-panel">
  <ng-template #eastPanel></ng-template>
</div>
Run Code Online (Sandbox Code Playgroud)

<button (click)="ShowPanel()">Open Panel</button>
Run Code Online (Sandbox Code Playgroud)

Emr*_*son 17

我喜欢苏杰的回答。如果您希望创建一个指令(在多个组件中使用)。我就是这样做的。

import {
  Directive,
  EventEmitter,
  HostListener,
  Output,
  ElementRef,
} from '@angular/core';

@Directive({
  selector: '[outsideClick]',
})
export class OutsideClickDirective {
  @Output()
  outsideClick: EventEmitter<MouseEvent> = new EventEmitter();

  @HostListener('document:mousedown', ['$event'])
  onClick(event: MouseEvent): void {
    if (!this.elementRef.nativeElement.contains(event.target)) {
      this.outsideClick.emit(event);
    }
  }

  constructor(private elementRef: ElementRef) {}
}
Run Code Online (Sandbox Code Playgroud)

然后您可以像这样使用该指令:

<div class="menu" *ngIf="isMenuOpen" (outsideClick)="isMenuOpen = false" outsideClick #menu>
  I'm the menu. Click outside to close me
</div>
Run Code Online (Sandbox Code Playgroud)


Suj*_*jay 15

你可以做这样的事情

  @HostListener('document:mousedown', ['$event'])
  onGlobalClick(event): void {
     if (!this.elementRef.nativeElement.contains(event.target)) {
        // clicked outside => close dropdown list
     this.isOpen = false;
     }
  }
Run Code Online (Sandbox Code Playgroud)

并为面板使用 *ngIf=isOpen

  • 什么是 if (!this.elementRef.nativeElement.contains(event.target))`? (2认同)

kys*_*elm 10

我想添加帮助我获得正确结果的解决方案。

当使用嵌入元素并且您想要检测父元素的点击时, event.target 提供对基本子元素的引用。

HTML

<div #toggleButton (click)="toggleMenu()">
    <u>Toggle Menu</u>
    <span class="some-icon"></span>
</div>

<div #menu class="menu" *ngIf="isMenuOpen">
    <h1>I'm the menu.</h1>
    <div>
        I have some complex content containing multiple children.
        <i>Click outside to close me</i>
    </div>
</div>
Run Code Online (Sandbox Code Playgroud)

我单击“切换菜单”文本,event.target 返回对'u'元素而不是#toggleButton div的引用

对于这种情况,我使用了 M98 的解决方案,包括 Renderer2,但将条件更改为 Sujay 的回答中的条件。

ToggleButton.nativeElement.contains(e.target)即使单击事件的目标在 nativeElement 的子级中也返回true,从而解决了问题。

组件.ts

export class AppComponent {
/**
 * This is the toogle button element, look at HTML and see its definition
 */
    @ViewChild('toggleButton') toggleButton: ElementRef;
    @ViewChild('menu') menu: ElementRef;
    isMenuOpen = false;

    constructor(private renderer: Renderer2) {
    /**
     * This events get called by all clicks on the page
     */
        this.renderer.listen('window', 'click',(e:Event)=>{
            /**
             * Only run when toggleButton is not clicked
             * If we don't check this, all clicks (even on the toggle button) gets into this
             * section which in the result we might never see the menu open!
             * And the menu itself is checked here, and it's where we check just outside of
             * the menu and button the condition abbove must close the menu
             */
            if(!this.toggleButton.nativeElement.contains(e.target) && !this.menu.nativeElement.contains(e.target)) {
                this.isMenuOpen=false;
            }
        });
    }

    toggleMenu() {
        this.isMenuOpen = !this.isMenuOpen;
    }
}
Run Code Online (Sandbox Code Playgroud)


M98*_*M98 9

我将通过使用Angular推荐的方法来做到这一点,该方法在没有DOM访问的环境中也很容易开发应用程序,我的意思是Renderer 2类是Angular提供的一种抽象形式,该形式是一种服务,该服务允许操作应用程序的元素而不必直接触摸DOM。

在这种方法中,您需要注入Renderer2到组件构造函数中,这Renderer2使我们能够listen优雅地触发事件。它只是需要的元素你要听上作为可成为第一个参数windowdocumentbody或任何其他元素的参考。对于第二个参数,它采用了我们将要监听的事件(在本例中为)click,而第三个参数实际上是我们通过箭头函数执行的回调函数。

this.renderer.listen('window', 'click',(e:Event)=>{ // your code here})
Run Code Online (Sandbox Code Playgroud)

解决方案的其余部分很容易,您只需要设置一个布尔标志即可保持菜单(或面板)状态的可见性,我们应该做的就是false在菜单外单击该标志时将其分配给该标志。

这是Stackblitz Demo的链接

的HTML

<button #toggleButton (click)="toggleMenu()"> Toggle Menu</button>

<div class="menu" *ngIf="isMenuOpen" #menu>
I'm the menu. Click outside to close me
</div>
Run Code Online (Sandbox Code Playgroud)

app.component.ts

export class AppComponent {
  /**
   * This is the toogle button elemenbt, look at HTML and see its defination
   */
  @ViewChild('toggleButton') toggleButton: ElementRef;
  @ViewChild('menu') menu: ElementRef;

  constructor(private renderer: Renderer2) {
    /**
     * This events get called by all clicks on the page
     */
    this.renderer.listen('window', 'click',(e:Event)=>{
         /**
          * Only run when toggleButton is not clicked
          * If we don't check this, all clicks (even on the toggle button) gets into this
          * section which in the result we might never see the menu open!
          * And the menu itself is checked here, and it's where we check just outside of
          * the menu and button the condition abbove must close the menu
          */
        if(e.target !== this.toggleButton.nativeElement && e.target!==this.menu.nativeElement){
            this.isMenuOpen=false;
        }
    });
  }

  isMenuOpen = false;

  toggleMenu() {
    this.isMenuOpen = !this.isMenuOpen;
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 如果您还需要检测目标子项上的点击事件,请使用:`!this.toggleButton.nativeElement.contains(e.target)`,如 /sf/answers/4017425891/ 上所述 (8认同)
  • 如果想防止菜单在单击菜单内部元素时关闭,请添加 `e.target !== this.toggleButton.nativeElement &amp;&amp; e.target !== this.menu.nativeElement &amp;&amp; !this.menu.nativeElement.contains(e .目标)` (4认同)
  • 由于冒泡,要检测出确切的点击位置可能会很麻烦。更好地使用`path`数组进行测试-全部包含!;-)`if(evt.path.indexOf(this.toggleButton.nativeElement)=== -1){...}` (2认同)

mic*_*ssi 8

这是一个可重用的指令,它还涵盖了元素位于 ngIf 内部的情况:

import { Directive, ElementRef, Optional, Inject, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
import { fromEvent, Subscription } from 'rxjs';
import { DOCUMENT } from '@angular/common';
import { filter } from 'rxjs/operators';

@Directive({
  selector: '[outsideClick]',
})
export class OutsideClickDirective implements OnInit, OnDestroy {
  @Output('outsideClick') outsideClick = new EventEmitter<MouseEvent>();

  private subscription: Subscription;

  constructor(private element: ElementRef, @Optional() @Inject(DOCUMENT) private document: any) {}

  ngOnInit() {
    setTimeout(() => {
      this.subscription = fromEvent<MouseEvent>(this.document, 'click')
        .pipe(
          filter(event => {
            const clickTarget = event.target as HTMLElement;
            return !this.isOrContainsClickTarget(this.element.nativeElement, clickTarget);
          }),
        )
        .subscribe(event => this.outsideClick.emit());
    }, 0);
  }

  private isOrContainsClickTarget(element: HTMLElement, clickTarget: HTMLElement) {
    return element == clickTarget || element.contains(clickTarget);
  }

  ngOnDestroy() {
    if (this.subscription) this.subscription.unsubscribe();
  }
}

Run Code Online (Sandbox Code Playgroud)

感谢https://github.com/ngez/platform,我从中得到了大部分逻辑。

我缺少的是 setTimeout(..., 0),它确保在使用该指令的组件呈现后安排检查。

有用的链接:


小智 6

更简化的代码与演示:StackBlitz

我制作了一个通用功能,可以在外部单击时关闭菜单,并在特定元素上触发单击时防止关闭。

超文本标记语言

<button (click)="toggleMenu(); preventCloseOnClick()">Toggle Menu</button>
<ul (click)="preventCloseOnClick()" *ngIf="menuOpen">
  <li>Menu 1</li>
  <li>Menu 2</li>
  <li>Menu 3</li>
  <li>Menu 4</li>
  <li>Menu 5</li>
</ul>
Run Code Online (Sandbox Code Playgroud)

TS

import { Component, VERSION, Renderer2 } from '@angular/core';

export class AppComponent {
  menuOpen: boolean = false;
  menuBtnClick: boolean = false;

  constructor(private renderer: Renderer2) {
    this.renderer.listen('window', 'click', (e: Event) => {
      if (!this.menuBtnClick) {
        this.menuOpen = false;
      }
      this.menuBtnClick = false;
    });
  }
  toggleMenu() {
    this.menuOpen = !this.menuOpen;
  }
  preventCloseOnClick() {
    this.menuBtnClick = true;
  }
}
Run Code Online (Sandbox Code Playgroud)