淘先锋技术网

首页 1 2 3 4 5 6 7

原文出处:https://www.jianshu.com/p/2c5a834b30c2  (建议阅读原文)

熟悉Angular7的同学都知道,在Angular7中想要监听组件内部的点击事件是非常简单的——只需要使用(click)事件绑定到组件,再向组件的实例中传入一个函数就搞定了。然而,当你想要监听组件外面的点击事件时,事情就变得很棘手了。而且这种情况在当你实现一些自定义的drop-down列表、上下文菜单以及pop-up组件时会经常遇到。所以,这个时候我们就应该实现一个可以复用的指令(directive)来实现监听组件外部的点击事件。
Angular7的语法正好提供了一个很好的方式来实现这样一个指令。下面我们就来看一下如何来实现这个简单的指令。

简述:指令(Directives)

在Angular7中,有三种类型的指令,分别是:动态组件、结构型指令(添加或移除DOM元素)和属性性指令(修改DOM元素的行为或表现)。在这里,我们想要切换一个DOM元素的行为,所以我们需要用后面的一种。
正如其名,我们要定义一个属性选择器名称用来区分这个指令。稍后,我们要像下面这样使用这个指令:

<ul class="contextMenu" (clickOutside)="close()"></ul>

所以,将clickOutside作为指令的属性选择器名称看起来是合理的:

import {Directive} from '@angular/core';
 
@Directive({
    selector: '[clickOutside]'
})
export class ClickOutsideDirective {
}

ElementRef

因为我们想要确定一个点击事件是否发生在当前的元素上面,所以需要获得clickOutside指令所在的DOM元素的引用。在Angular7中,我们可以通过在指令的构造方法中传入一个ElementRef来得到这个引用。

import {Directive, ElementRef} from '@angular/core';
 
@Directive({
    selector: '[clickOutside]'
})
export class ClickOutsideDirective {
    constructor(private _elementRef : ElementRef) {
    }
}

通过在构造方法的参数列表中使用private访问修饰符,当前指令中将会自动创建一个与这个变量同名的属性。
这时监听组件外部点击事件的算法将会变得非常简单:我们将截获页面上任何元素的所有点击事件,然后检查其是否发生在我们当前指令所在的元素上面。即如果点击没有发生在我们自己的元素上,这个点击一定发生在元素的外面。就是这么简单。
注意:在ElementRef的文档中提到,当访问DOM元素时,要尽量避免使用上面的API。因为模板语言或数据绑定在大多数情况下无疑都是更好的选择。除此之外,那些方法在服务端渲染或是其他渲染器的情况下也是有效的。在这里,检查点击事件是否在特定的DOM元素上发生只会发生在浏览器中,所以ElementRef在这种特殊的情况下是可以使用的。

事件绑定(Event Binding)

接下来,我们来实现事件绑定。你可能已经猜到了,这个实现非常简单:我们只需要创建一个EventEmitter类型的属性就可以了。一般情况下EventEmitter会携带一个待传出的参数。不过在大多数情况下,能监听到元素外部的点击事件已经足够。因此,我们在这里不会传出任何值。它会在每次组件外部点击时调用。
为了使得EventEmitter作为事件绑定在外部是可见的,我们需要在这个属性上加上Output注解。这个注解带了一个可选的参数,这个参数允许为这个事件绑定指定一个别名。如果你没有传入参数,那么将会默认使用属性名代替。

import {Directive, ElementRef, Output, EventEmitter} from '@angular/core';
 
@Directive({
    selector: '[clickOutside]'
})
export class ClickOutsideDirective {
    constructor(private _elementRef : ElementRef) {
    }
 
    @Output()
    public clickOutside = new EventEmitter();
}

HostListener

我们现在已经有了一个可以调用的事件了。接下来让我们实现这个指令的核心逻辑。
Angular7中的HostListener注解让我们能够监听宿主(如DOM元素)上的特定事件。通过使用docoment:记号,你还可以监听document级别的事件。这正是我们要去做的,即截取页面上任何元素的点击事件并且检查其是否发生在我们的元素的外部。
此外,你还可以定义那些参数需要被传入被修饰的方法中。这个事件的参数是一个MouseEvent,而且还在target属性中包含了一个目标元素。因为我们只对当前元素感兴趣,所以简单地将这个元素传入即可:

import {Directive, ElementRef, Output, EventEmitter, HostListener} from '@angular/core';
 
@Directive({
    selector: '[clickOutside]'
})
export class ClickOutsideDirective {
    constructor(private _elementRef : ElementRef) {
    }
 
    @Output()
    public clickOutside = new EventEmitter();
 
    @HostListener('document:click', ['$event.target'])
    public onClick(targetElement) {
    }
}

其中的onClick方法将会在每次文档被点击时调用。由于使用了HostListener,你并不需要去手动解绑这个事件,Angular已经帮我们做好了。

大功告成

现在让我们检查被点击的目标元素是否被包含在指令所在的元素当中。这时我们使用DOM API中的contains函数来实现它。如果给出的元素节点与另一个节点相同或是另一个元素的子节点,那么这个函数就会返回true

import {Directive, ElementRef, Output, EventEmitter, HostListener} from '@angular/core';

@Directive({
   selector: '[clickOutside]'
})
export class ClickOutsideDirective {
   constructor(private _elementRef : ElementRef) {
   }

   @Output()
   public clickOutside = new EventEmitter();

   @HostListener('document:click', ['$event.target'])
   public onClick(targetElement) {
       const clickedInside = this._elementRef.nativeElement.contains(targetElement);
       if (!clickedInside) {
           this.clickOutside.emit(null);
       }
   }
}

至此,我们便实现了在Angular7中监听组件外部点击事件的指令。



作者:neromaycry
链接:https://www.jianshu.com/p/2c5a834b30c2
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。