我正在构建指令,该指令应该在元素输入视口时添加类,并且还将触发自定义事件。我找到了两种触发事件的方法 - EventEmitter
和dispatchEvent()
,两者都运行正常。在这种情况下应该使用哪个以及为什么? (对代码的任何其他建议表示赞赏)
import { EventEmitter, Directive, ElementRef, Renderer2, OnInit } from '@angular/core';
import { HostListener } from "@angular/core";
import { Component, Input, Output, Inject, PLATFORM_ID, ViewChild, ViewEncapsulation } from "@angular/core";
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { AfterViewInit } from '@angular/core/src/metadata/lifecycle_hooks';
@Directive({
selector: '[animateOnVisible]',
})
export class AnimateOnVisibleDirective implements AfterViewInit {
@Input() animateOnVisible: string = "fadeInUp";
@Output() enteredViewport: EventEmitter<string> = new EventEmitter();
public isBrowser: boolean;
private enableListener: boolean = true;
constructor(private renderer: Renderer2, private hostElement: ElementRef, @Inject(PLATFORM_ID) private platformId: any) {
this.isBrowser = isPlatformBrowser(platformId);
}
@HostListener("window:scroll", [])
onWindowScroll() {
this.checkScrollPosition();
}
ngAfterViewInit() {
this.checkScrollPosition();
}
private checkScrollPosition() {
if (this.isBrowser && this.enableListener && window.scrollY + window.innerHeight / 2 >= this.hostElement.nativeElement.offsetTop) {
this.renderer.addClass(this.hostElement.nativeElement, this.animateOnVisible);
this.enableListener = false;
//triggering custom event
this.enteredViewport.emit("");
//OR
this.hostElement.nativeElement.dispatchEvent(new Event('enteredViewport', { bubbles: true }));
}
}
}
<div class="animated" [animateOnVisible]="'test'" (enteredViewport)="test()">
答案 0 :(得分:3)
EventEmitter
用于可用于Angular事件绑定的@Output()
<my-component (myEvent)="doSomething()"
dispatchEvent()
触发一个DOM事件,也可以绑定为Angular @Output()
事件显示的事件,但也可以冒出DOM树。
前者特定于Angular,并且用于更有效的用例,后者的行为与其他DOM事件一样,也可以由非Angular代码监听,但可能效率较低。
答案 1 :(得分:1)
组合解决方案也是可能的。考虑具有用于删除请求的EventEmitter的子组件:
onDeleteRequest: EventEmitter<GridElementDeleteRequestEvent> =
new EventEmitter<GridElementDeleteRequestEvent>();
子组件侦听键盘事件以发出GridElementDeleteRequestEvent
本身,并由父组件拾取。
@HostListener('keyup', ['$event']) private keyUpHandler(e: KeyboardEvent) {
if (e.key === 'Delete') {
this.onDeleteRequest.emit(new GridElementDeleteRequestEvent(this._gridElement.id));
}
父组件订阅它:
<app-ipe-grid-element (onDeleteRequest)="this.gridElementDeleteRequestHandler($event)">
处理程序具有以下实现:
public gridElementDeleteRequestHandler(e: GridElementDeleteRequestEvent) {
// code
...
}
在孩子身上有一个更深层次的内部组件嵌套结构。其中一个内部组件是一个上下文相关菜单,它还可以删除所谓的GridElement
(这个故事中的孩子)。
为了防止将所有EventEmitter从嵌套组件绑定到彼此的繁琐架构,上下文敏感菜单将调度“常规”DOM事件,如下所示:
const event: CustomEvent =
new CustomEvent('GridElementDeleteRequestEvent',
{
bubbles: true,
cancelable: true,
detail: new GridElementDeleteRequestEvent(
this._gridElement.gridElement.id)});
this.nativeElement.dispatchEvent(event);
为了解决这个问题,父组件的处理程序只需要用HostListener
指令进行修饰,并在类型(instanceof
)上检查传入事件,当它是CustomEvent
时详细信息将转换为GridElementDeleteRequestEvent
,如下所示:
@HostListener('GridElementDeleteRequestEvent', ['$event'])
public gridElementDeleteRequestHandler(e: CustomEvent) {
const customEvent: GridElementDeleteRequestEvent = e instanceof GridElementDeleteRequestEvent ?
e :
<GridElementDeleteRequestEvent>e.detail;
// code
...
使用此方法,直接(EventEmitter
)和间接(DOM调度)事件都在父级的一个事件处理程序中处理。
注意强>
当然,如果孩子的EventEmitter
不应该被完全删除而且孩子的键盘事件处理程序也应该像这样派遣DOM Event
,那么这就提出了一个问题:
@HostListener('keyup', ['$event']) private keyUpHandler(e: KeyboardEvent) {
if (e.key === 'Delete') {
const event: CustomEvent = new CustomEvent(
'GridElementDeleteRequestEvent',
{
bubbles: true,
cancelable: true,
detail: new GridElementDeleteRequestEvent(this.gridElement.id)
});
this.nativeElement.dispatchEvent(event);
}
这将使实现更简单,并且考虑来自不同“位置”的相同事件(不同级别的多个嵌套元素)。
保持“直接”EventEmitter(在这种特殊情况下)的一个微小论点可能来自@GünterZöchbauer的答案,其中EventEmitter应该(略微)更有效率。这个答案来源的用例不涉及数十个GridElementDeletRequestEvent
,因此保留EventEmitter的效果可以忽略不计。
从多个Angular组件中触发GridElementDeleteRequestEvent的要求以及保持代码尽可能简单的要求比EventEmitter的假设稍微更有效的行为更重要。