Html 检测 Angular 组件外的点击
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/40107008/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Detect click outside Angular component
提问by AMagyar
How can I detect clicks outsidea component in Angular?
如何检测Angular 组件外的点击?
回答by AMagyar
import { Component, ElementRef, HostListener, Input } from '@angular/core';
@Component({
selector: 'selector',
template: `
<div>
{{text}}
</div>
`
})
export class AnotherComponent {
public text: String;
@HostListener('document:click', ['$event'])
clickout(event) {
if(this.eRef.nativeElement.contains(event.target)) {
this.text = "clicked inside";
} else {
this.text = "clicked outside";
}
}
constructor(private eRef: ElementRef) {
this.text = 'no clicks yet';
}
}
回答by J. Frankenstein
An alternative to AMagyar's answer. This version works when you click on element that gets removed from the DOM with an ngIf.
AMagyar 答案的替代方法。当您单击使用 ngIf 从 DOM 中删除的元素时,此版本有效。
http://plnkr.co/edit/4mrn4GjM95uvSbQtxrAS?p=preview
http://plnkr.co/edit/4mrn4GjM95uvSbQtxrAS?p=preview
private wasInside = false;
@HostListener('click')
clickInside() {
this.text = "clicked inside";
this.wasInside = true;
}
@HostListener('document:click')
clickout() {
if (!this.wasInside) {
this.text = "clicked outside";
}
this.wasInside = false;
}
回答by ginalx
Binding to document click through @Hostlistener is costly. It can and will have a visible performance impact if you overuse(for example, when building a custom dropdown component and you have multiple instances created in a form).
通过@Hostlistener 绑定到文档点击的成本很高。如果您过度使用(例如,在构建自定义下拉组件并且您在表单中创建了多个实例时),它可能并且将会对性能产生明显影响。
I suggest adding a @Hostlistener() to the document click event only once inside your main app component. The event should push the value of the clicked target element inside a public subject stored in a global utility service.
我建议只在主应用程序组件内向文档单击事件添加一次 @Hostlistener()。该事件应该将单击的目标元素的值推送到存储在全局实用程序服务中的公共主题中。
@Component({
selector: 'app-root',
template: '<router-outlet></router-outlet>'
})
export class AppComponent {
constructor(private utilitiesService: UtilitiesService) {}
@HostListener('document:click', ['$event'])
documentClick(event: any): void {
this.utilitiesService.documentClickedTarget.next(event.target)
}
}
@Injectable({ providedIn: 'root' })
export class UtilitiesService {
documentClickedTarget: Subject<HTMLElement> = new Subject<HTMLElement>()
}
Whoever is interested for the clicked target element should subscribe to the public subject of our utilities service and unsubscribe when the component is destroyed.
对被点击的目标元素感兴趣的人应该订阅我们公用事业服务的公共主题,并在组件被销毁时取消订阅。
export class AnotherComponent implements OnInit {
@ViewChild('somePopup', { read: ElementRef, static: false }) somePopup: ElementRef
constructor(private utilitiesService: UtilitiesService) { }
ngOnInit() {
this.utilitiesService.documentClickedTarget
.subscribe(target => this.documentClickListener(target))
}
documentClickListener(target: any): void {
if (this.somePopup.nativeElement.contains(target))
// Clicked inside
else
// Clicked outside
}
回答by Rishanthakumar
Above mentioned answers are correct but what if you are doing a heavy process after losing the focus from the relevant component. For that, I came with a solution with two flags where the focus out event process will only take place when losing the focus from relevant component only.
上面提到的答案是正确的,但是如果您在失去相关组件的焦点后正在做一个繁重的过程怎么办。为此,我提出了一个带有两个标志的解决方案,其中焦点移出事件过程只会在仅从相关组件失去焦点时发生。
isFocusInsideComponent = false;
isComponentClicked = false;
@HostListener('click')
clickInside() {
this.isFocusInsideComponent = true;
this.isComponentClicked = true;
}
@HostListener('document:click')
clickout() {
if (!this.isFocusInsideComponent && this.isComponentClicked) {
// do the heavy process
this.isComponentClicked = false;
}
this.isFocusInsideComponent = false;
}
Hope this will help you. Correct me If have missed anything.
希望这会帮助你。纠正我 如果错过了什么。
回答by James Bond
You can useclickOutside() method from https://www.npmjs.com/package/ng-click-outsidepackage
您可以使用https://www.npmjs.com/package/ng-click-outside包中的clickOutside() 方法
回答by edoardo849
ginalx's answershould be set as the default one imo: this method allows for many optimizations.
ginalx 的答案应设置为默认的一个 imo:此方法允许进行许多优化。
The problem
问题
Say that we have a list of items and on every item we want to include a menu that needs to be toggled. We include a toggle on a button that listens for a click
event on itself (click)="toggle()"
, but we also want to toggle the menu whenever the user clicks outside of it. If the list of items grows and we attach a @HostListener('document:click')
on every menu, then every menu loaded within the item will start listening for the click on the entire document, even when the menu is toggled off. Besides the obvious performance issues, this is unnecessary.
假设我们有一个项目列表,并且在每个项目上我们想要包含一个需要切换的菜单。我们在一个按钮上添加了一个开关,它监听click
自身的事件(click)="toggle()"
,但我们也想在用户点击菜单外时切换菜单。如果项目列表增长并且我们@HostListener('document:click')
在每个菜单上附加一个,那么项目中加载的每个菜单都将开始监听对整个文档的点击,即使菜单被关闭。除了明显的性能问题之外,这是不必要的。
You can, for example, subscribe whenever the popup gets toggled via a click and start listening for "outside clicks" only then.
例如,您可以在弹出窗口通过单击切换时订阅,然后才开始收听“外部点击”。
isActive: boolean = false;
// to prevent memory leaks and improve efficiency, the menu
// gets loaded only when the toggle gets clicked
private _toggleMenuSubject$: BehaviorSubject<boolean>;
private _toggleMenu$: Observable<boolean>;
private _toggleMenuSub: Subscription;
private _clickSub: Subscription = null;
constructor(
...
private _utilitiesService: UtilitiesService,
private _elementRef: ElementRef,
){
...
this._toggleMenuSubject$ = new BehaviorSubject(false);
this._toggleMenu$ = this._toggleMenuSubject$.asObservable();
}
ngOnInit() {
this._toggleMenuSub = this._toggleMenu$.pipe(
tap(isActive => {
logger.debug('Label Menu is active', isActive)
this.isActive = isActive;
// subscribe to the click event only if the menu is Active
// otherwise unsubscribe and save memory
if(isActive === true){
this._clickSub = this._utilitiesService.documentClickedTarget
.subscribe(target => this._documentClickListener(target));
}else if(isActive === false && this._clickSub !== null){
this._clickSub.unsubscribe();
}
}),
// other observable logic
...
).subscribe();
}
toggle() {
this._toggleMenuSubject$.next(!this.isActive);
}
private _documentClickListener(targetElement: HTMLElement): void {
const clickedInside = this._elementRef.nativeElement.contains(targetElement);
if (!clickedInside) {
this._toggleMenuSubject$.next(false);
}
}
ngOnDestroy(){
this._toggleMenuSub.unsubscribe();
}
And, in *.component.html
:
并且,在*.component.html
:
<button (click)="toggle()">Toggle the menu</button>