如何在外部点击时关闭下拉列表?下拉列表组件:应用程序组件:什么困扰我:

2022-08-30 02:12:54

当用户单击该下拉列表之外的任意位置时,我想关闭我的登录菜单下拉列表,并且我想使用Angular2和Angular2“方法”执行此操作...

我已经实施了一个解决方案,但我真的对它没有信心。我认为必须有一种最简单的方法来达到相同的结果,所以如果你有任何想法......让我们讨论:)!

这是我的实现:

下拉列表组件:

这是我的下拉列表的组件:

  • 每当这个组件设置为可见时(例如:当用户点击一个按钮来显示它时),它就会订阅存储在 SubjectsService 中的“全局”rxjs subject userMenu
  • 每次它被隐藏时,它都会取消订阅此主题。
  • 每次单击此组件模板中的任意位置都会触发 onClick() 方法,该方法只会阻止事件冒泡到顶部(和应用程序组件)

这是代码

export class UserMenuComponent {

    _isVisible: boolean = false;
    _subscriptions: Subscription<any> = null;

    constructor(public subjects: SubjectsService) {
    }

    onClick(event) {
        event.stopPropagation();
    }

    set isVisible(v) {
        if( v ){
            setTimeout( () => {
this._subscriptions =  this.subjects.userMenu.subscribe((e) => {
                       this.isVisible = false;
                       })
            }, 0);
        } else {
            this._subscriptions.unsubscribe();
        }
        this._isVisible = v;
    }

    get isVisible() {
        return this._isVisible;
    }
}

应用程序组件:

另一方面,有应用程序组件(它是下拉列表组件的父级):

  • 此组件捕获每个单击事件,并在同一 rxjs 主题 (userMenu) 上发出)

代码如下:

export class AppComponent {

    constructor( public subjects: SubjectsService) {
        document.addEventListener('click', () => this.onClick());
    }
    onClick( ) {
        this.subjects.userMenu.next({});
    }
}

什么困扰我:

  1. 我对拥有一个全球主题作为这些组件之间的连接器的想法感到不舒服。
  2. setTimeout:这是必需的,因为如果用户单击显示下拉列表的按钮,则会发生以下情况:
    • 用户单击按钮(不是下拉列表组件的一部分)以显示下拉列表。
    • 将显示下拉列表,并立即订阅 userMenu 主题
    • 单击事件冒泡到应用组件并被捕获
    • 应用程序组件在用户菜单主题上发出事件
    • 下拉列表组件在 userMenu 上捕获此操作并隐藏下拉列表。
    • 最后,下拉列表永远不会显示。

这个设置的超时延迟了订阅到当前JavaScript代码的结束,从而解决了这个问题,但在我看来,以一种非常优雅的方式。

如果您知道更清洁,更好,更智能,更快或更强大的解决方案,请让我知道:)!


答案 1

您可以使用事件:(document:click)

@Component({
  host: {
    '(document:click)': 'onClick($event)',
  },
})
class SomeComponent() {
  constructor(private _eref: ElementRef) { }

  onClick(event) {
   if (!this._eref.nativeElement.contains(event.target)) // or some similar check
     doSomething();
  }
}

另一种方法是创建自定义事件作为指令。看看Ben Nadel的这些帖子:


答案 2

优雅的方法

我发现这个指令:https://github.com/chliebel/angular2-click-outside。我检查它,它工作得很好(我只复制到我的项目)。你可以用这种方式使用它:clickOutclickOutside.directive.ts

<div (clickOutside)="close($event)"></div>

当用户单击外部 div 时将调用的函数在哪里。这是处理所讨论的问题的非常优雅的方式。close

如果您使用上述指令关闭弹出窗口,请记住首先要添加到打开弹出窗口的按钮单击事件处理程序。event.stopPropagation()

奖金:

下面我从文件中复制了oryginal指令代码(以防链接将来停止工作) - 作者是Christian LiebelclickOutside.directive.ts

import {Directive, ElementRef, Output, EventEmitter, HostListener} from '@angular/core';
    
@Directive({
    selector: '[clickOutside]'
})
export class ClickOutsideDirective {
    constructor(private _elementRef: ElementRef) {
    }

    @Output()
    public clickOutside = new EventEmitter<MouseEvent>();

    @HostListener('document:click', ['$event', '$event.target'])
    public onClick(event: MouseEvent, targetElement: HTMLElement): void {
        if (!targetElement) {
            return;
        }

        const clickedInside = this._elementRef.nativeElement.contains(targetElement);
        if (!clickedInside) {
            this.clickOutside.emit(event);
        }
    }
}