检测角度分量外的点击

我如何检测点击 在外面角度分量?

185763 次浏览
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';
}
}

工作示例-点击这里

AMagyar 的回答的替代品。当您使用 ngIf 单击从 DOM 中删除的元素时,此版本可以正常工作。

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

您可以使用 外面包中的 clickOutside()方法; 它提供了一个指令“用于处理元素外部的单击事件”。

注意: 此软件包目前已被废弃。请参阅 https://github.com/arkon/ng-sidebar/issues/229了解更多信息。

前面的答案是正确的,但是如果您在失去相关组件的关注之后正在进行一个繁重的过程,那么该怎么办呢?为此,我提出了一个带有两个标志的解决方案,其中只有在从相关组件失去焦点时,焦点外移事件过程才会发生。

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 processing


this.isComponentClicked = false;
}
this.isFocusInsideComponent = false;
}

通过 @ 主持人听众单击绑定到文档的成本很高。如果您过度使用它,它可以而且将会产生可见的性能影响(例如,在构建自定义下拉组件时,您在一个表单中创建了多个实例)。

我建议在你的主应用程序组件中只在文档点击事件中添加一次 @ 主持人听众()。事件应该将单击的目标元素的值推送到存储在全局实用程序服务中的公共主题中。

@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>()
}

任何对被点击的目标元素感兴趣的人都应该订阅我们公用事业服务的公共主题,并在组件被销毁时取消订阅。

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
}

Ginalx 的答案 应该设置为默认的一个 imo: 这个方法允许进行许多优化。

问题是

假设我们有一个项目列表,在每个项目上我们想包括一个需要被切换的菜单。我们在一个按钮上包含一个切换,该按钮在自身 (click)="toggle()"上侦听一个 click事件,但是我们也希望在用户单击外部时切换菜单。如果项目列表增长,并且我们在每个菜单上附加一个 @HostListener('document:click'),那么在项目中加载的每个菜单将开始监听对整个文档的点击,即使菜单被关闭。除了明显的性能问题,这是不必要的。

例如,只要弹出窗口通过点击被切换,你就可以订阅,然后开始监听“外部点击”。


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


*.component.html中:


<button (click)="toggle()">Toggle the menu</button>


改善 J · 弗兰肯斯坦的回答:

  @HostListener('click')
clickInside($event) {
this.text = "clicked inside";
$event.stopPropagation();
}


@HostListener('document:click')
clickOutside() {
this.text = "clicked outside";
}

你可以调用一个事件函数,比如(focus out)或者(模糊) ; 然后你可以输入你的代码:

<div tabindex=0 (blur)="outsideClick()">raw data </div>


outsideClick() {
alert('put your condition here');
}

使用 Event的另一种可能的解决方案是:

  1. 在最上面的父组件上定义一个 click 侦听器,它清除 click-inside 变量
  2. 在子组件上定义一个 click 侦听器,这个子组件首先调用 event.stop  () ,然后设置 click-inside 变量

除了 MVP,你只需要观看事件

@HostListener('focusout', ['$event'])
protected onFocusOut(event: FocusEvent): void {
console.log(
'click away from component? :',
event.currentTarget && event.relatedTarget
);
}

解决方案

把所有家长都叫来

var paths       = event['path'] as Array<any>;

检查是否有父组件

var inComponent = false;
paths.forEach(path => {
if (path.tagName != undefined) {
var tagName = path.tagName.toString().toLowerCase();
if (tagName == 'app-component')
inComponent = true;
}
});

如果您将组件作为父组件,那么单击组件内部

if (inComponent) {
console.log('clicked inside');
}else{
console.log('clicked outside');
}

完整的方法

@HostListener('document:click', ['$event'])
clickout(event: PointerEvent) {
    

var paths       = event['path'] as Array<any>;
    

var inComponent = false;
paths.forEach(path => {
if (path.tagName != undefined) {
var tagName = path.tagName.toString().toLowerCase();
if (tagName == 'app-component')
inComponent = true;
}
});
    

if (inComponent) {
console.log('clicked inside');
}else{
console.log('clicked outside');
}
    

}