提问者:小点点

如何关闭点击外面的下拉列表?


当用户单击下拉列表之外的任何位置时,我想关闭我的登录菜单下拉列表,我想使用Angular2和Angular2“方法”来做到这一点……

我实现了一个解决方案,但我真的对它没有信心。我认为一定有一个最简单的方法可以达到同样的结果,所以如果你有任何想法…让我们讨论:)!

这是我的实现:

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

  • 每次这个组件设置为可见时,(例如:当用户点击一个按钮显示它时)它都会订阅一个“全局”rxjs主题userMenu存储在SubjectsService中。
  • 每次被隐藏,它都会取消订阅这个主题。
  • 该组件模板内的每一次点击都会触发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({});
    }
}
  • 我对有一个全局主体作为这些组件之间的连接器的想法感到不舒服。
  • setTimeout:这是需要的,因为如果用户单击显示下拉列表的按钮,则会发生以下情况:
    • 用户单击按钮(不是下拉组件的一部分)以显示下拉列表。
    • 显示下拉列表并立即订阅用户菜单主题。
    • 点击事件冒泡到应用组件并被捕获
    • 应用程序组件在userMenu主题上发出事件
    • 下拉组件在userMenu上捕获此操作并隐藏下拉列表。
    • 最后永远不会显示下拉列表。

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

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


共3个答案

匿名用户

您可以使用(文档:点击)事件:

@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的这些帖子:

  • 跟踪-点击-事件-外部-当前-组件
  • 选择器和输出可以有相同的名称
  • 指令元数据
  • 主机绑定

匿名用户

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

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

其中close是您的函数,当用户单击div外部时将调用它。这是处理问题中描述的问题的非常优雅的方法。

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

下面我从文件复制原始指令代码clickOutside. Directive.ts(如果链接将来会停止工作)-作者是Christian Liebel:

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);
        }
    }
}

匿名用户

我已经这样做了。

在文档上添加了一个事件侦听器,单击并在该处理程序中检查我的容器是否包含event.target,如果没有-隐藏下拉列表。

它看起来像这样。

@Component({})
class SomeComponent {
    @ViewChild('container') container;
    @ViewChild('dropdown') dropdown;

    constructor() {
        document.addEventListener('click', this.offClickHandler.bind(this)); // bind on doc
    }

    offClickHandler(event:any) {
        if (!this.container.nativeElement.contains(event.target)) { // check click origin
            this.dropdown.nativeElement.style.display = "none";
        }
    }
}