如何关闭点击外部的下拉菜单?
How can I close a dropdown on click outside?
我想在用户单击该下拉菜单之外的任何地方时关闭我的登录菜单下拉菜单,我想用 Angular2 和 Angular2 "approach"...
我已经实施了一个解决方案,但我真的对它没有信心。我认为必须有一种最简单的方法来实现相同的结果,所以如果您有任何想法......让我们讨论一下:) !
这是我的实现:
下拉组件:
这是我的下拉菜单的组件:
- 每次此组件设置为可见时(例如:当用户单击按钮以显示它时)它订阅一个 "global" 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 主题
上发出事件
- 下拉组件在 userMenu 上捕获此操作并隐藏下拉列表。
- 最后,下拉列表永远不会显示。
这个设置的超时延迟订阅到当前 JavaScript 代码轮次的结束,这解决了问题,但在我看来是一种非常优雅的方式。
如果您知道更清洁、更好、更智能、更快或更强大的解决方案,请告诉我:) !
您可以使用 (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 的这些帖子:
我已经这样做了。
在文档 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";
}
}
}
我们今天在工作中一直在处理类似的问题,试图弄清楚如何使下拉菜单 div 在单击时消失。我们的问题与最初发布者的问题略有不同,因为我们不想点击不同的 component 或 directive,而只是在特别是 div.
我们最终使用 (window:mouseup) 事件处理程序解决了这个问题。
步骤:
1.) 我们给整个下拉菜单 div 一个唯一的 class 名称。
2.) 在内部下拉菜单本身(我们希望点击不关闭菜单的唯一部分),我们添加了一个 (window:mouseup) 事件处理程序并传入了 $event。
注意:无法使用典型的 "click" 处理程序完成,因为这与父单击处理程序冲突。
3.) 在我们的控制器中,我们创建了我们希望在点击事件上调用的方法,我们使用 event.closest (docs here) 来确定点击的位置是否在我们的目标范围内-class 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>
如果您正在使用 Bootstrap,您可以通过下拉菜单(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
您可以为覆盖整个屏幕的下拉菜单创建同级元素,该元素是不可见的,仅用于捕获点击事件。然后您可以检测对该元素的点击并在点击时关闭下拉菜单。假设元素是 class 丝印,这里是它的一些样式:
.silkscreen {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
}
z-index 需要足够高才能将其置于除下拉列表之外的所有内容之上。在这种情况下,我的下拉列表将是 b z-index 2.
其他答案在某些情况下对我有用,除了有时当我与其中的元素交互时我的下拉列表关闭但我不希望这样。正如我预期的那样,我根据事件目标动态添加了组件中未包含的元素。我没有整理这些乱七八糟的东西,而是想用丝网印刷的方式尝试一下。
我想补充@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] 应用适当的 类...
我认为 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 目标路径)中。这样就可以处理动态创建的元素。
优雅的方法
我找到了这个 clickOut
指令:
https://github.com/chliebel/angular2-click-outside。我检查了它,它运行良好(我只将 clickOutside.directive.ts
复制到我的项目中)。你可以这样使用它:
<div (clickOutside)="close($event)"></div>
其中 close
是您的函数,当用户在 div 外部单击时将调用该函数。这是处理问题中描述的问题的非常优雅的方式。
如果您使用上述指令关闭弹出窗口 window,请记住首先将 event.stopPropagation()
添加到打开弹出窗口的按钮单击事件处理程序。
奖金:
下面我从文件 clickOutside.directive.ts
复制了原始指令代码(如果 link 将来会停止工作)-作者是 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);
}
}
}
@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 文件中://如果使用 bootstrap 下拉菜单则不需要。
.ourDropdown{
display: none;
}
.ourDropdown.open{
display: inherit;
}
我自己也做了一些解决方法。
我创建了一个 (dropdownOpen) 事件,我在我的 ng-select 元素组件中收听并调用函数将关闭除当前打开的 SelectComponent 之外所有其他打开的 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();
})
}
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;
}
}
缺点:
- 页面上的每个组件都有两个点击事件监听器。不要在页面上出现数百次的组件上使用它。
你可以写指令:
@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' 时,该指令发出事件。
它监听 mousedown 事件以在元素从 DOM.
中删除之前处理事件
还有一个注意事项:
firefox 不包含 属性 'path' 你可以使用函数创建路径的事件:
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 事件。
你应该检查一下你是否点击了模态叠加层,这样就容易多了。
您的模板:
<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();
}
}
如果您在 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();
}
}
}
我没有做任何解决方法。我刚刚在我的切换功能上附加了 document:click,如下所示:
@Directive({
selector: '[appDropDown]'
})
export class DropdownDirective implements OnInit {
@HostBinding('class.open') isOpen: boolean;
constructor(private elemRef: ElementRef) { }
ngOnInit(): void {
this.isOpen = false;
}
@HostListener('document:click', ['$event'])
@HostListener('document:touchstart', ['$event'])
toggle(event) {
if (this.elemRef.nativeElement.contains(event.target)) {
this.isOpen = !this.isOpen;
} else {
this.isOpen = false;
}
}
因此,当我超出指令范围时,我会关闭下拉菜单。
注意: 对于那些想要使用 web worker 并且您需要避免使用 document 和 nativeElement 的人来说,这会起作用。
我在这里回答了同样的问题:
Copy/Paste 从上面 link:
我在制作下拉菜单和确认对话框时遇到了同样的问题,我想在点击外部时关闭它们。
我的最终实现非常完美,但需要一些 css3 动画和样式。
注意:我没有测试过下面的代码,可能有一些语法问题需要解决,还有你自己项目的明显调整!
我做了什么:
我做了一个单独的固定 div 高度 100%,宽度 100% 和 transform:scale(0),这基本上是背景,你可以用背景颜色设置样式: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);
}
正确答案有问题,如果你的弹出窗口中有一个可点击的组件,该元素将不再在 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();
}
}
}
感谢您的帮助!
我已经制定了一个指令来解决这个类似的问题,我正在使用 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>
最优雅的方法:D
有一种最简单的方法可以做到这一点,不需要任何指令。
"element-that-toggle-your-dropdown" 应该是按钮标签。使用(模糊)属性中的任何方法。就这些了。
<button class="element-that-toggle-your-dropdown"
(blur)="isDropdownOpen = false"
(click)="isDropdownOpen = !isDropdownOpen">
</button>
我遇到了另一个解决方案,灵感来自 focus/blur 事件的示例。
因此,如果您想在不附加全局文档侦听器的情况下实现相同的功能,您可以考虑以下示例。它也适用于 OSx 上的 Safari 和 Firefox,尽管它们对按钮焦点事件有其他处理方式:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
stackbiz 上的工作示例 angular 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();
}
}
您可以像这样在视图中使用 mouseleave
使用 angular 8 测试并完美运行
<ul (mouseleave)="closeDropdown()"> </ul>
我决定根据我的用例post我自己的解决方案。我在 Angular 11 中有一个带有(单击)事件的 href。这会切换主要 app.ts 中的菜单组件 on off/
<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;
}
这意味着单击“命名”上下文之外的任何地方 closes/hides“命名”组件。
这是 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;
}
}
}
我认为没有足够的答案,所以我想参与其中。这就是我所做的
component.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>
我想在用户单击该下拉菜单之外的任何地方时关闭我的登录菜单下拉菜单,我想用 Angular2 和 Angular2 "approach"...
我已经实施了一个解决方案,但我真的对它没有信心。我认为必须有一种最简单的方法来实现相同的结果,所以如果您有任何想法......让我们讨论一下:) !
这是我的实现:
下拉组件:
这是我的下拉菜单的组件:
- 每次此组件设置为可见时(例如:当用户单击按钮以显示它时)它订阅一个 "global" 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 主题 上发出事件
- 下拉组件在 userMenu 上捕获此操作并隐藏下拉列表。
- 最后,下拉列表永远不会显示。
这个设置的超时延迟订阅到当前 JavaScript 代码轮次的结束,这解决了问题,但在我看来是一种非常优雅的方式。
如果您知道更清洁、更好、更智能、更快或更强大的解决方案,请告诉我:) !
您可以使用 (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 的这些帖子:
我已经这样做了。
在文档 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";
}
}
}
我们今天在工作中一直在处理类似的问题,试图弄清楚如何使下拉菜单 div 在单击时消失。我们的问题与最初发布者的问题略有不同,因为我们不想点击不同的 component 或 directive,而只是在特别是 div.
我们最终使用 (window:mouseup) 事件处理程序解决了这个问题。
步骤:
1.) 我们给整个下拉菜单 div 一个唯一的 class 名称。
2.) 在内部下拉菜单本身(我们希望点击不关闭菜单的唯一部分),我们添加了一个 (window:mouseup) 事件处理程序并传入了 $event。
注意:无法使用典型的 "click" 处理程序完成,因为这与父单击处理程序冲突。
3.) 在我们的控制器中,我们创建了我们希望在点击事件上调用的方法,我们使用 event.closest (docs here) 来确定点击的位置是否在我们的目标范围内-class 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>
如果您正在使用 Bootstrap,您可以通过下拉菜单(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
您可以为覆盖整个屏幕的下拉菜单创建同级元素,该元素是不可见的,仅用于捕获点击事件。然后您可以检测对该元素的点击并在点击时关闭下拉菜单。假设元素是 class 丝印,这里是它的一些样式:
.silkscreen {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
}
z-index 需要足够高才能将其置于除下拉列表之外的所有内容之上。在这种情况下,我的下拉列表将是 b z-index 2.
其他答案在某些情况下对我有用,除了有时当我与其中的元素交互时我的下拉列表关闭但我不希望这样。正如我预期的那样,我根据事件目标动态添加了组件中未包含的元素。我没有整理这些乱七八糟的东西,而是想用丝网印刷的方式尝试一下。
我想补充@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] 应用适当的 类...
我认为 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 目标路径)中。这样就可以处理动态创建的元素。
优雅的方法
我找到了这个 clickOut
指令:
https://github.com/chliebel/angular2-click-outside。我检查了它,它运行良好(我只将 clickOutside.directive.ts
复制到我的项目中)。你可以这样使用它:
<div (clickOutside)="close($event)"></div>
其中 close
是您的函数,当用户在 div 外部单击时将调用该函数。这是处理问题中描述的问题的非常优雅的方式。
如果您使用上述指令关闭弹出窗口 window,请记住首先将 event.stopPropagation()
添加到打开弹出窗口的按钮单击事件处理程序。
奖金:
下面我从文件 clickOutside.directive.ts
复制了原始指令代码(如果 link 将来会停止工作)-作者是 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);
}
}
}
@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 文件中://如果使用 bootstrap 下拉菜单则不需要。
.ourDropdown{
display: none;
}
.ourDropdown.open{
display: inherit;
}
我自己也做了一些解决方法。
我创建了一个 (dropdownOpen) 事件,我在我的 ng-select 元素组件中收听并调用函数将关闭除当前打开的 SelectComponent 之外所有其他打开的 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();
})
}
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;
}
}
缺点: - 页面上的每个组件都有两个点击事件监听器。不要在页面上出现数百次的组件上使用它。
你可以写指令:
@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' 时,该指令发出事件。 它监听 mousedown 事件以在元素从 DOM.
中删除之前处理事件还有一个注意事项: firefox 不包含 属性 'path' 你可以使用函数创建路径的事件:
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 事件。
你应该检查一下你是否点击了模态叠加层,这样就容易多了。
您的模板:
<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();
}
}
如果您在 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();
}
}
}
我没有做任何解决方法。我刚刚在我的切换功能上附加了 document:click,如下所示:
@Directive({ selector: '[appDropDown]' }) export class DropdownDirective implements OnInit { @HostBinding('class.open') isOpen: boolean; constructor(private elemRef: ElementRef) { } ngOnInit(): void { this.isOpen = false; } @HostListener('document:click', ['$event']) @HostListener('document:touchstart', ['$event']) toggle(event) { if (this.elemRef.nativeElement.contains(event.target)) { this.isOpen = !this.isOpen; } else { this.isOpen = false; } }
因此,当我超出指令范围时,我会关闭下拉菜单。
注意: 对于那些想要使用 web worker 并且您需要避免使用 document 和 nativeElement 的人来说,这会起作用。
我在这里回答了同样的问题:
Copy/Paste 从上面 link:
我在制作下拉菜单和确认对话框时遇到了同样的问题,我想在点击外部时关闭它们。
我的最终实现非常完美,但需要一些 css3 动画和样式。
注意:我没有测试过下面的代码,可能有一些语法问题需要解决,还有你自己项目的明显调整!
我做了什么:
我做了一个单独的固定 div 高度 100%,宽度 100% 和 transform:scale(0),这基本上是背景,你可以用背景颜色设置样式: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);
}
正确答案有问题,如果你的弹出窗口中有一个可点击的组件,该元素将不再在 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();
}
}
}
感谢您的帮助!
我已经制定了一个指令来解决这个类似的问题,我正在使用 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>
最优雅的方法:D
有一种最简单的方法可以做到这一点,不需要任何指令。
"element-that-toggle-your-dropdown" 应该是按钮标签。使用(模糊)属性中的任何方法。就这些了。
<button class="element-that-toggle-your-dropdown"
(blur)="isDropdownOpen = false"
(click)="isDropdownOpen = !isDropdownOpen">
</button>
我遇到了另一个解决方案,灵感来自 focus/blur 事件的示例。
因此,如果您想在不附加全局文档侦听器的情况下实现相同的功能,您可以考虑以下示例。它也适用于 OSx 上的 Safari 和 Firefox,尽管它们对按钮焦点事件有其他处理方式:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
stackbiz 上的工作示例 angular 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();
}
}
您可以像这样在视图中使用 mouseleave
使用 angular 8 测试并完美运行
<ul (mouseleave)="closeDropdown()"> </ul>
我决定根据我的用例post我自己的解决方案。我在 Angular 11 中有一个带有(单击)事件的 href。这会切换主要 app.ts 中的菜单组件 on off/
<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;
}
这意味着单击“命名”上下文之外的任何地方 closes/hides“命名”组件。
这是 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;
}
}
}
我认为没有足够的答案,所以我想参与其中。这就是我所做的
component.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>