【问题标题】:How can I close a dropdown on click outside?如何在外部点击时关闭下拉菜单?
【发布时间】:2016-06-13 05:59:01
【问题描述】:

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

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

这是我的实现:

下拉组件:

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

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

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

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

【问题讨论】:

标签: javascript drop-down-menu angular rxjs


【解决方案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 的这些帖子:

【讨论】:

  • @Sasxa 谢谢,并同意。我想如果有一个未弃用的 API 文档,它会出现在将我带到这里的搜索中。
  • 如果 event.target 是通过 [innerHTML] 绑定等动态添加的元素,则 elementRef 的 nativeElement 将不包含它。
  • 这种技术的唯一缺点是现在您的应用程序中有一个单击事件侦听器,每次单击时都会触发。
  • 根据官方 Angular 2 风格指南,您应该在 Component 装饰器上使用 @HostListener('document:click', ['$event']) 而不是 host 属性。
  • 或者你可以直接使用 rxjs,比如Observable.fromEvent(document, 'click').subscribe(event =&gt; {your code here}),这样你就可以只在你需要收听的时候订阅,例如你打开下拉菜单,当你关闭它时你取消订阅
【解决方案2】:

我是这样做的。

在文档 click 上添加了一个事件侦听器,并在该处理程序中检查我的 container 是否包含 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";
        }
    }
}

【讨论】:

  • 嗨。 .bind(this) 有必要吗?
  • @Brian 这可能是也可能不是必要的,但如果他将this.offClickHandler 包装在箭头函数中,则绝对不会。
【解决方案3】:

我们今天在工作中一直在研究一个类似的问题,试图弄清楚如何使下拉 div 在单击时消失。我们的问题与最初发布者的问题略有不同,因为我们不想点击离开不同的组件指令,而只是在特定的div之外。

我们最终通过使用 (window:mouseup) 事件处理程序解决了这个问题。

步骤:
1.) 我们为整个下拉菜单 div 赋予了一个唯一的类名。

2.) 在内部下拉菜单本身(我们希望单击而不关闭菜单的唯一部分)上,我们添加了一个 (window:mouseup) 事件处理程序并传入 $event。

注意:不能使用典型的“点击”处理程序来完成,因为这与父点击处理程序冲突。

3.) 在我们的控制器中,我们创建了希望在点击事件上调用的方法,并且我们使用 event.closest (docs here) 来确定点击的位置是否在我们的目标类 div 中。

 autoCloseForDropdownCars(event) {
        var target = event.target;
        if (!target.closest(".DropdownCars")) { 
            // do whatever you want here
        }
    }
 <div class="DropdownCars">
   <span (click)="toggleDropdown(dropdownTypes.Cars)" class="searchBarPlaceholder">Cars</span>
   <div class="criteriaDropdown" (window:mouseup)="autoCloseForDropdownCars($event)" *ngIf="isDropdownShown(dropdownTypes.Cars)">
   </div>
</div>

【讨论】:

  • "window:mouseup" 应该在宿主装饰器中使用。
  • @Shivam--我不确定你所说的“应该在宿主装饰器中使用”是什么意思。你能进一步解释一下吗?谢谢!
  • 我的意思是不要直接使用“window”对象,而应该使用组件装饰器的“host”属性/组件的“HostListener”装饰器。这是在角度 2 中使用“窗口”或“文档”对象时的标准做法。
  • 请注意浏览器的兼容性,.closest() 目前在 IE/Edge 上不支持 (caniuse)
【解决方案4】:

如果您使用的是 Bootstrap,您可以通过下拉菜单(Bootstrap 组件)直接使用引导方式。

<div class="input-group">
    <div class="input-group-btn">
        <button aria-expanded="false" aria-haspopup="true" class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">
            Toggle Drop Down. <span class="fa fa-sort-alpha-asc"></span>
        </button>
        <ul class="dropdown-menu">
            <li>List 1</li>
            <li>List 2</li>
            <li>List 3</li>
        </ul>
    </div>
</div>

现在可以将(click)="clickButton()" 的东西放在按钮上。 http://getbootstrap.com/javascript/#dropdowns

【讨论】:

    【解决方案5】:

    您可以为覆盖整个屏幕的下拉菜单创建一个兄弟元素,该元素将不可见,仅用于捕获点击事件。然后,您可以检测对该元素的点击,并在点击它时关闭下拉菜单。假设元素是丝网印刷的,这里有一些风格:

    .silkscreen {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        z-index: 1;
    }
    

    z-index 需要足够高,才能将其置于除下拉列表之外的所有内容之上。在这种情况下,我的下拉菜单将是 b z-index 2。

    其他答案在某些情况下对我有用,除了有时我的下拉菜单在我与其中的元素交互时关闭并且我不希望那样。我根据事件目标动态添加了组件中不包含的元素,就像我预期的那样。与其整理那些乱七八糟的东西,我想我只是尝试丝网印刷的方式。

    【讨论】:

      【解决方案6】:

      我想补充@Tony 的答案,因为在组件外部单击后不会删除该事件。完整收据:

      • 用#container标记你的主要元素

        @ViewChild('container') container;
        
        _dropstatus: boolean = false;
        get dropstatus() { return this._dropstatus; }
        set dropstatus(b: boolean) 
        {
            if (b) { document.addEventListener('click', this.offclickevent);}
            else { document.removeEventListener('click', this.offclickevent);}
            this._dropstatus = b;
        }
        offclickevent: any = ((evt:any) => { if (!this.container.nativeElement.contains(evt.target)) this.dropstatus= false; }).bind(this);
        
      • 在可点击元素上,使用:

        (click)="dropstatus=true"
        

      现在您可以使用 dropstatus 变量控制下拉状态,并使用 [ngClass] 应用适当的类...

      【讨论】:

        【解决方案7】:

        我认为 Sasxa 接受的答案适用于大多数人。但是,我遇到了一种情况,应该监听非点击事件的元素的内容会动态变化。因此,元素 nativeElement 在动态创建时不包含 event.target。 我可以用以下指令解决这个问题

        @Directive({
          selector: '[myOffClick]'
        })
        export class MyOffClickDirective {
        
          @Output() offClick = new EventEmitter();
        
          constructor(private _elementRef: ElementRef) {
          }
        
          @HostListener('document:click', ['$event.path'])
          public onGlobalClick(targetElementPath: Array<any>) {
            let elementRefInPath = targetElementPath.find(e => e === this._elementRef.nativeElement);
            if (!elementRefInPath) {
              this.offClick.emit(null);
            }
          }
        }
        

        我不是检查 elementRef 是否包含 event.target,而是检查 elementRef 是否在事件的路径(目标的 DOM 路径)中。这样就可以处理动态创建的元素。

        【讨论】:

        • 谢谢 - 当子组件存在时效果更好
        • 这对我很有帮助。不知道为什么其他答案没有检测到组件外部的点击。
        【解决方案8】:

        优雅的方法

        我发现了这个clickOut 指令: https://github.com/chliebel/angular2-click-outside。我检查了一下,它运行良好(我只将clickOutside.directive.ts 复制到我的项目中)。你可以这样使用它:

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

        close 是您的函数,当用户单击 div 外部时将调用该函数。这是处理问题描述的问题的一种非常优雅的方式。

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

        奖金:

        下面我从文件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);
                }
            }
        }

        【讨论】:

        • @Vega 我的建议是在带有 *ngIf 的元素中使用指令,在下拉列表的情况下,这可能类似于 &lt;div class="wrap" *ngIf="isOpened" (clickOutside)="...// this should set this.isOpen=false"
        【解决方案9】:

        @Tony 的一个更好的解决方案:

        @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.closest(".ourDropdown.open").classList.remove("open");
        
                }
            }
        }
        

        在 css 文件中://如果使用引导下拉菜单则不需要。

        .ourDropdown{
           display: none;
        }
        .ourDropdown.open{
           display: inherit;
        }
        

        【讨论】:

          【解决方案10】:

          我自己也做了一些解决方法。

          我创建了一个 (dropdownOpen) 事件,我在我的 ng-select 元素组件中收听该事件并调用一个函数,该函数将关闭除当前打开的 SelectComponent。

          我修改了 select.ts 文件中的一个函数,如下所示:

          private open():void {
              this.options = this.itemObjects
                  .filter((option:SelectItem) => (this.multiple === false ||
                  this.multiple === true && !this.active.find((o:SelectItem) => option.text === o.text)));
          
              if (this.options.length > 0) {
                  this.behavior.first();
              }
              this.optionsOpened = true;
              this.dropdownOpened.emit(true);
          }
          

          在 HTML 中,我为 (dropdownOpened) 添加了一个事件监听器:

          <ng-select #elem (dropdownOpened)="closeOtherElems(elem)"
              [multiple]="true"
              [items]="items"
              [disabled]="disabled"
              [isInputAllowed]="true"
              (data)="refreshValue($event)"
              (selected)="selected($event)"
              (removed)="removed($event)"
              placeholder="No city selected"></ng-select>
          

          这是我在具有 ng2-select 标记的组件内的事件触发器上的调用函数:

          @ViewChildren(SelectComponent) selectElem :QueryList<SelectComponent>;
          
          public closeOtherElems(element){
              let a = this.selectElem.filter(function(el){
                          return (el != element)
                      });
          
              a.forEach(function(e:SelectComponent){
                  e.closeDropdown();
              })
          }
          

          【讨论】:

            【解决方案11】:
            import { Component, HostListener } from '@angular/core';
            
            @Component({
                selector: 'custom-dropdown',
                template: `
                    <div class="custom-dropdown-container">
                        Dropdown code here
                    </div>
                `
            })
            export class CustomDropdownComponent {
                thisElementClicked: boolean = false;
            
                constructor() { }
            
                @HostListener('click', ['$event'])
                onLocalClick(event: Event) {
                    this.thisElementClicked = true;
                }
            
                @HostListener('document:click', ['$event'])
                onClick(event: Event) {
                    if (!this.thisElementClicked) {
                        //click was outside the element, do stuff
                    }
                    this.thisElementClicked = false;
                }
            }
            

            缺点: - 页面上每个组件的两个单击事件侦听器。不要在页面上数百次的组件上使用它。

            【讨论】:

            • 没有,我只在桌面浏览器上使用过。
            【解决方案12】:

            你可以写指令:

            @Directive({
              selector: '[clickOut]'
            })
            export class ClickOutDirective implements AfterViewInit {
              @Input() clickOut: boolean;
            
              @Output() clickOutEvent: EventEmitter<any> = new EventEmitter<any>();
            
              @HostListener('document:mousedown', ['$event']) onMouseDown(event: MouseEvent) {
            
                   if (this.clickOut && 
                     !event.path.includes(this._element.nativeElement))
                   {
                       this.clickOutEvent.emit();
                   }
              } 
            
            
            }
            

            在您的组件中:

            @Component({
              selector: 'app-root',
              template: `
                <h1 *ngIf="isVisible" 
                  [clickOut]="true" 
                  (clickOutEvent)="onToggle()"
                >{{title}}</h1>
            `,
              styleUrls: ['./app.component.scss'],
              changeDetection: ChangeDetectionStrategy.OnPush
            })
            export class AppComponent {
              title = 'app works!';
            
              isVisible = false;
            
              onToggle() {
                this.isVisible = !this.isVisible;
              }
            }
            

            当 html 元素包含在 DOM 中并且 [clickOut] 输入属性为 'true' 时,此指令会发出事件。 在元素从 DOM 中移除之前,它会监听 mousedown 事件来处理事件。

            还有一点: firefox 在事件中不包含属性“路径”,您可以使用函数创建路径:

            const getEventPath = (event: Event): HTMLElement[] => {
              if (event['path']) {
                return event['path'];
              }
              if (event['composedPath']) {
                return event['composedPath']();
              }
              const path = [];
              let node = <HTMLElement>event.target;
              do {
                path.push(node);
              } while (node = node.parentElement);
              return path;
            };
            

            因此,您应该更改指令上的事件处理程序: event.path 应该被替换 getEventPath(event)

            这个模块可以提供帮助。 https://www.npmjs.com/package/ngx-clickout 它包含相同的逻辑,但也处理源 html 元素上的 esc 事件。

            【讨论】:

              【解决方案13】:

              你应该检查你是否点击了模态覆盖,这更容易。

              您的模板:

              <div #modalOverlay (click)="clickOutside($event)" class="modal fade show" role="dialog" style="display: block;">
                      <div class="modal-dialog" [ngClass]='size' role="document">
                          <div class="modal-content" id="modal-content">
                              <div class="close-modal" (click)="closeModal()"> <i class="fa fa-times" aria-hidden="true"></i></div>
                              <ng-content></ng-content>
                          </div>
                      </div>
                  </div>
              

              以及方法:

                  @ViewChild('modalOverlay') modalOverlay: ElementRef;
                  
                  // ... your constructor and other methods
                  
                  clickOutside(event: Event) {
                      const target = event.target || event.srcElement;
                      console.log('click', target);
                      console.log("outside???", this.modalOverlay.nativeElement == event.target)
                      // const isClickOutside = !this.modalBody.nativeElement.contains(event.target);
                      // console.log("click outside ?", isClickOutside);
                      if ("isClickOutside") {
                        // this.closeModal();
                      }
                  }
              

              【讨论】:

                【解决方案14】:

                如果您在 iOS 上执行此操作,请同时使用 touchstart 事件:

                从 Angular 4 开始,HostListener 装饰是执行此操作的首选方式

                import { Component, OnInit, HostListener, ElementRef } from '@angular/core';
                ...
                @Component({...})
                export class MyComponent implement OnInit {
                
                  constructor(private eRef: ElementRef){}
                
                  @HostListener('document:click', ['$event'])
                  @HostListener('document:touchstart', ['$event'])
                  handleOutsideClick(event) {
                    // Some kind of logic to exclude clicks in Component.
                    // This example is borrowed Kamil's answer
                    if (!this.eRef.nativeElement.contains(event.target) {
                      doSomethingCool();
                    }
                  }
                
                }
                

                【讨论】:

                  【解决方案15】:

                  我没有解决任何问题。我刚刚附上了文件:点击我的切换功能如下:

                  @指示({ 选择器:'[appDropDown]' }) 导出类 DropdownDirective 实现 OnInit { @HostBinding('class.open') isOpen: boolean; 构造函数(私有 elemRef:ElementRef){ } ngOnInit(): 无效 { this.isOpen = false; } @HostListener('document:click', ['$event']) @HostListener('document:touchstart', ['$event']) 切换(事件){ if (this.elemRef.nativeElement.contains(event.target)) { this.isOpen = !this.isOpen; } 别的 { this.isOpen = false; } }

                  所以,当我在我的指令之外时,我会关闭下拉菜单。

                  【讨论】:

                    【解决方案16】:

                    注意:对于那些想要使用网络工作者并且您需要避免使用 document 和 nativeElement 的人,这将起作用。

                    我在这里回答了同样的问题:https://stackoverflow.com/questions/47571144

                    从以上链接复制/粘贴:

                    我在制作下拉菜单和确认对话框时遇到了同样的问题,我想在点击外部时将其关闭。

                    我的最终实现完美运行,但需要一些 css3 动画和样式。

                    注意:我没有测试过下面的代码,可能有一些语法问题需要解决,还有你自己项目的明显调整!

                    我做了什么:

                    我做了一个单独的固定 div,高度 100%,宽度 100% 和 transform:scale(0),这本质上是背景,你可以用 background-color: rgba(0, 0, 0, 0.466) ;使菜单明显打开,背景是点击关闭。 菜单的 z-index 高于其他所有内容,然后背景 div 的 z-index 低于菜单但也高于其他所有内容。然后后台有一个关闭下拉菜单的点击事件。

                    这是您的 html 代码。

                    <div class="dropdownbackground" [ngClass]="{showbackground: qtydropdownOpened}" (click)="qtydropdownOpened = !qtydropdownOpened"><div>
                    <div class="zindex" [class.open]="qtydropdownOpened">
                      <button (click)="qtydropdownOpened = !qtydropdownOpened" type="button" 
                             data-toggle="dropdown" aria-haspopup="true" [attr.aria-expanded]="qtydropdownOpened ? 'true': 'false' ">
                       {{selectedqty}}<span class="caret margin-left-1x "></span>
                     </button>
                      <div class="dropdown-wrp dropdown-menu">
                      <ul class="default-dropdown">
                          <li *ngFor="let quantity of quantities">
                           <a (click)="qtydropdownOpened = !qtydropdownOpened;setQuantity(quantity)">{{quantity  }}</a>
                           </li>
                       </ul>
                      </div>
                     </div>
                    

                    这是需要一些简单动画的css3。

                    /* make sure the menu/drop-down is in front of the background */
                    .zindex{
                        z-index: 3;
                    }
                    
                    /* make background fill the whole page but sit behind the drop-down, then
                    scale it to 0 so its essentially gone from the page */
                    .dropdownbackground{
                        width: 100%;
                        height: 100%;
                        position: fixed;
                        z-index: 2;
                        transform: scale(0);
                        opacity: 0;
                        background-color: rgba(0, 0, 0, 0.466);
                    }
                    
                    /* this is the class we add in the template when the drop down is opened
                    it has the animation rules set these how you like */
                    .showbackground{
                        animation: showBackGround 0.4s 1 forwards; 
                    
                    }
                    
                    /* this animates the background to fill the page
                    if you don't want any thing visual you could use a transition instead */
                    @keyframes showBackGround {
                        1%{
                            transform: scale(1);
                            opacity: 0;
                        }
                        100% {
                            transform: scale(1);
                            opacity: 1;
                        }
                    }
                    

                    如果你不追求任何视觉效果,你可以使用这样的过渡

                    .dropdownbackground{
                        width: 100%;
                        height: 100%;
                        position: fixed;
                        z-index: 2;
                        transform: scale(0);
                        opacity: 0;
                        transition all 0.1s;
                    }
                    
                    .dropdownbackground.showbackground{
                         transform: scale(1);
                    }
                    

                    【讨论】:

                      【解决方案17】:

                      正确答案有问题,如果你的popover中有clicakble组件,该元素将不再在contain方法上并且会关闭,基于我自己创建的@JuHarm89:

                      export class PopOverComponent implements AfterViewInit {
                       private parentNode: any;
                      
                        constructor(
                          private _element: ElementRef
                        ) { }
                      
                        ngAfterViewInit(): void {
                          this.parentNode = this._element.nativeElement.parentNode;
                        }
                      
                        @HostListener('document:click', ['$event.path'])
                        onClickOutside($event: Array<any>) {
                          const elementRefInPath = $event.find(node => node === this.parentNode);
                          if (!elementRefInPath) {
                            this.closeEventEmmit.emit();
                          }
                        }
                      }
                      

                      感谢您的帮助!

                      【讨论】:

                        【解决方案18】:

                        我已经制定了一个指令来解决这个类似的问题,并且我正在使用 Bootstrap。但在我的情况下,与其等待元素外的点击事件来关闭当前打开的下拉菜单,我认为我们最好监视“mouseleave”事件以自动关闭菜单。

                        这是我的解决方案:

                        指令

                        import { Directive, HostListener, HostBinding } from '@angular/core';
                        @Directive({
                          selector: '[appDropdown]'
                        })
                        export class DropdownDirective {
                        
                          @HostBinding('class.open') isOpen = false;
                        
                          @HostListener('click') toggleOpen() {
                            this.isOpen = !this.isOpen;
                          }
                        
                          @HostListener('mouseleave') closeDropdown() {
                            this.isOpen = false;
                          }
                        
                        }
                        

                        HTML

                        <ul class="nav navbar-nav navbar-right">
                            <li class="dropdown" appDropdown>
                              <a class="dropdown-toggle" data-toggle="dropdown">Test <span class="caret"></span>
                              </a>
                              <ul class="dropdown-menu">
                                  <li routerLinkActive="active"><a routerLink="/test1">Test1</a></li>
                                  <li routerLinkActive="active"><a routerLink="/test2/">Test2</a></li>
                              </ul>
                            </li>
                        </ul>
                        

                        【讨论】:

                          【解决方案19】:

                          最优雅的方法:D

                          有一种最简单的方法可以做到这一点,不需要任何指令。

                          "element-that-toggle-your-dropdown" 应该是按钮标签。在 (blur) 属性中使用任何方法。就是这样。

                          <button class="element-that-toggle-your-dropdown"
                                         (blur)="isDropdownOpen = false"
                                         (click)="isDropdownOpen = !isDropdownOpen">
                          </button>
                          

                          【讨论】:

                          • 如果你想让下拉菜单在点击时保持打开状态,这将不起作用,例如用户可能会错过点击按钮
                          【解决方案20】:

                          受焦点/模糊事件示例的启发,我遇到了另一个解决方案。

                          因此,如果您想在不附加全局文档侦听器的情况下实现相同的功能,您可以考虑以下示例。它也适用于 OSx 上的 Safari 和 Firefox,尽管它们对按钮焦点事件有其他处理:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus

                          stackbiz 上的工作示例,角度为 8:https://stackblitz.com/edit/angular-sv4tbi?file=src%2Ftoggle-dropdown%2Ftoggle-dropdown.directive.ts

                          HTML 标记:

                          <div class="dropdown">
                            <button class="btn btn-secondary dropdown-toggle" type="button" aria-haspopup="true" aria-expanded="false">Dropdown button</button>
                            <div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
                              <a class="dropdown-item" href="#">Action</a>
                              <a class="dropdown-item" href="#">Another action</a>
                              <a class="dropdown-item" href="#">Something else here</a>
                            </div>
                          </div>
                          
                          

                          指令将如下所示:

                          import { Directive, HostBinding, ElementRef, OnDestroy, Renderer2 } from '@angular/core';
                          
                          @Directive({
                            selector: '.dropdown'
                          })
                          export class ToggleDropdownDirective {
                          
                            @HostBinding('class.show')
                            public isOpen: boolean;
                          
                            private buttonMousedown: () => void;
                            private buttonBlur: () => void;
                            private navMousedown: () => void;
                            private navClick: () => void;
                          
                            constructor(private element: ElementRef, private renderer: Renderer2) { }
                          
                            ngAfterViewInit() {
                              const el = this.element.nativeElement;
                              const btnElem = el.querySelector('.dropdown-toggle');
                              const menuElem = el.querySelector('.dropdown-menu');
                          
                              this.buttonMousedown = this.renderer.listen(btnElem, 'mousedown', (evt) => {
                                console.log('MOUSEDOWN BTN');
                                this.isOpen = !this.isOpen;
                                evt.preventDefault(); // prevents loose of focus (default behaviour) on some browsers
                              });
                          
                              this.buttonMousedown = this.renderer.listen(btnElem, 'click', () => {
                                console.log('CLICK BTN');
                                // firefox OSx, Safari, Ie OSx, Mobile browsers.
                                // Whether clicking on a <button> causes it to become focused varies by browser and OS.
                                btnElem.focus();
                              });
                          
                              // only for debug
                              this.buttonMousedown = this.renderer.listen(btnElem, 'focus', () => {
                                console.log('FOCUS BTN');
                              });
                          
                              this.buttonBlur = this.renderer.listen(btnElem, 'blur', () => {
                                console.log('BLUR BTN');
                                this.isOpen = false;
                              });
                          
                              this.navMousedown = this.renderer.listen(menuElem, 'mousedown', (evt) => {
                                console.log('MOUSEDOWN MENU');
                                evt.preventDefault(); // prevents nav element to get focus and button blur event to fire too early
                              });
                              this.navClick = this.renderer.listen(menuElem, 'click', () => {
                                console.log('CLICK MENU');
                                this.isOpen = false;
                                btnElem.blur();
                              });
                            }
                          
                            ngOnDestroy() {
                              this.buttonMousedown();
                              this.buttonBlur();
                              this.navMousedown();
                              this.navClick();
                            }
                          }
                          

                          【讨论】:

                          • 由于安全原因以及任何 Angular 应用程序的松散耦合设计,Angular 团队强烈反对使用 ElementRef。
                          【解决方案21】:

                          您可以像这样在视图中使用mouseleave

                          使用 angular 8 测试并完美运行

                          <ul (mouseleave)="closeDropdown()"> </ul>
                          

                          【讨论】:

                          • 这将在鼠标离开时关闭容器,但无论如何感谢您的分享,因为我不知道它的存在。
                          【解决方案22】:

                          我决定根据我的用例发布自己的解决方案。我在 Angular 11 中有一个带有 (click) 事件的 href。这将主 app.ts 中的菜单组件切换为关闭/

                          <li><a href="javascript:void(0)" id="menu-link" (click)="toggleMenu();" ><img id="menu-image" src="img/icons/menu-white.png" ></a></li>
                          

                          菜单组件(例如 div)基于名为“isMenuVisible”的布尔值是可见的 (*ngIf)。当然,它可以是下拉菜单或任何组件。

                          在 app.ts 我有这个简单的功能

                          @HostListener('document:click', ['$event'])
                          onClick(event: Event) {
                          
                              const elementId = (event.target as Element).id;
                              if (elementId.includes("menu")) {
                                  return;
                              }
                          
                              this.isMenuVisble = false;
                          
                          }
                          

                          这意味着单击“命名”上下文之外的任何位置都会关闭/隐藏“命名”组件。

                          【讨论】:

                            【解决方案23】:

                            这是在组件外部关闭的 Angular Bootstrap DropDowns 按钮示例。

                            不用bootstrap.js

                            // .html
                            <div class="mx-3 dropdown" [class.show]="isTestButton">
                              <button class="btn dropdown-toggle"
                                      (click)="isTestButton = !isTestButton">
                                <span>Month</span>
                              </button>
                              <div class="dropdown-menu" [class.show]="isTestButton">
                                <button class="btn dropdown-item">Month</button>
                                <button class="btn dropdown-item">Week</button>
                              </div>
                            </div>
                            
                            // .ts
                            import { Component, ElementRef, HostListener } from "@angular/core";
                            
                            @Component({
                              selector: "app-test",
                              templateUrl: "./test.component.html",
                              styleUrls: ["./test.component.scss"]
                            })
                            export class TestComponent {
                            
                              isTestButton = false;
                            
                              constructor(private eleRef: ElementRef) {
                              }
                            
                            
                              @HostListener("document:click", ["$event"])
                              docEvent($e: MouseEvent) {
                                if (!this.isTestButton) {
                                  return;
                                }
                                const paths: Array<HTMLElement> = $e["path"];
                                if (!paths.some(p => p === this.eleRef.nativeElement)) {
                                  this.isTestButton = false;
                                }
                              }
                            }
                            

                            【讨论】:

                              【解决方案24】:

                              我认为没有足够的答案,所以我想加入。这就是我所做的

                              组件.ts

                              @Component({
                                  selector: 'app-issue',
                                  templateUrl: './issue.component.html',
                                  styleUrls: ['./issue.component.sass'],
                              })
                              export class IssueComponent {
                                  @Input() issue: IIssue;
                                  @ViewChild('issueRef') issueRef;
                                  
                                  public dropdownHidden = true;
                                  
                                  constructor(private ref: ElementRef) {}
                              
                                  public toggleDropdown($event) {
                                      this.dropdownHidden = !this.dropdownHidden;
                                  }
                                  
                                  @HostListener('document:click', ['$event'])
                                  public hideDropdown(event: any) {
                                      if (!this.dropdownHidden && !this.issueRef.nativeElement.contains(event.target)) {
                                          this.dropdownHidden = true;
                                      }
                                  }
                              }
                              

                              component.html

                              <div #issueRef (click)="toggleDropdown()">
                                  <div class="card card-body">
                                      <p class="card-text truncate">{{ issue.fields.summary }}</p>
                                      <div class="d-flex justify-content-between">
                                          <img
                                              *ngIf="issue.fields.assignee; else unassigned"
                                              class="rounded"
                                              [src]="issue.fields.assignee.avatarUrls['32x32']"
                                              [alt]="issue.fields.assignee.displayName"
                                          />
                                          <ng-template #unassigned>
                                              <img
                                                  class="rounded"
                                                  src="https://img.icons8.com/pastel-glyph/2x/person-male--v2.png"
                                                  alt="Unassigned"
                                              />
                                          </ng-template>
                                          <img
                                              *ngIf="issue.fields.priority"
                                              class="rounded mt-auto priority"
                                              [src]="issue.fields.priority.iconUrl"
                                              [alt]="issue.fields.priority.name"
                                          />
                                      </div>
                                  </div>
                                  <div *ngIf="!dropdownHidden" class="list-group context-menu">
                                      <a href="#" class="list-group-item list-group-item-action active" aria-current="true">
                                          The current link item
                                      </a>
                                      <a href="#" class="list-group-item list-group-item-action">A second link item</a>
                                      <a href="#" class="list-group-item list-group-item-action">A third link item</a>
                                      <a href="#" class="list-group-item list-group-item-action">A fourth link item</a>
                                      <a
                                          href="#"
                                          class="list-group-item list-group-item-action disabled"
                                          tabindex="-1"
                                          aria-disabled="true"
                                          >A disabled link item</a
                                      >
                                  </div>
                              </div>
                              

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 1970-01-01
                                • 2018-04-20
                                • 2019-10-16
                                • 2019-08-02
                                • 2017-02-12
                                • 2017-03-23
                                • 2023-02-23
                                相关资源
                                最近更新 更多