Angular 4 触发自定义事件 - EventEmitter 与 dispatchEvent()

Angular 4 trigger custom event - EventEmitter vs dispatchEvent()

我正在构建指令,该指令应在元素进入视口时添加 class,并且还会触发自定义事件。我找到了 2 种触发事件的方法 - EventEmitterdispatchEvent(),两者都可以正常工作。在这种情况下应该使用哪个,为什么? (对代码的任何其他建议表示赞赏)

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()">

EventEmitter用于@Output()可用于Angular事件绑定

<my-component (myEvent)="doSomething()"

dispatchEvent() 触发一个 DOM 事件,也可以像 Angular @Output() 事件那样绑定,但也可以冒泡 DOM 树.

前者特定于 Angular,对于更高效的预期用例,后者的行为类似于其他 DOM 事件,也可以被 non-Angular 代码监听,但是可能效率较低。

组合解决方案也是可能的。考虑一个 child 组件,它有一个用于删除请求的 EventEmitter:

onDeleteRequest: EventEmitter<GridElementDeleteRequestEvent> = 
  new EventEmitter<GridElementDeleteRequestEvent>();

child 组件侦听键盘事件以发出 GridElementDeleteRequestEvent 自身,这些事件由 parent 组件接收。

@HostListener('keyup', ['$event']) private keyUpHandler(e: KeyboardEvent) {
  if (e.key === 'Delete') {
    this.onDeleteRequest.emit(new GridElementDeleteRequestEvent(this._gridElement.id));
}

一个 parent 组件订阅了它:

<app-ipe-grid-element (onDeleteRequest)="this.gridElementDeleteRequestHandler($event)">

其中处理程序具有以下实现:

public gridElementDeleteRequestHandler(e: GridElementDeleteRequestEvent) {
  // code
  ...
}

在child中有更深层次的内部组件嵌套结构。这些内部组件之一是上下文相关菜单,它还提供删除 so-called GridElement(本故事中的 child)的可能性。

为了防止繁琐的架构将嵌套组件中的所有 EventEmitter 相互绑定,上下文相关菜单会分派一个 "regular" DOM 事件,如下所示:

const event: CustomEvent = 
  new CustomEvent('GridElementDeleteRequestEvent',
    {
      bubbles: true,
      cancelable: true,
      detail: new GridElementDeleteRequestEvent(
        this._gridElement.gridElement.id)});

this.nativeElement.dispatchEvent(event);

为了解决这个问题,parent 组件的处理程序只需要用 HostListener 指令修饰,并根据类型 (instanceof) 检查传入事件,当它是CustomEvent 详细信息转换为 GridElementDeleteRequestEvent,如下所示:

@HostListener('GridElementDeleteRequestEvent', ['$event'])
public gridElementDeleteRequestHandler(e: CustomEvent) {
const customEvent: GridElementDeleteRequestEvent = e instanceof GridElementDeleteRequestEvent ? 
  e : 
  <GridElementDeleteRequestEvent>e.detail;

  // code
  ...

通过这种方法,直接 (EventEmitter) 和间接 (DOM 调度) 事件都在 parent.

上的一个事件处理程序中处理

备注

当然这会引发一个问题,如果 child 处的 EventEmitter 不应该被完全删除并且 child 的键盘事件处理程序也应该只发送一个 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);
}

这将使实施更加简单,并说明来自不同 "places"(不同级别的多个嵌套元素)的相同事件。

保留 "direct" EventEmitter(在这种特殊情况下)的一个小论点可能来自@Günter Zöchbauer 的回答,其中 EventEmitter 应该(稍微)更有效。此答案的来源用例不涉及数十个 GridElementDeletRequestEvent,因此保留 EventEmitter 的效果可以忽略不计。

从多个 Angular 组件触发 GridElementDeleteRequestEvent 的要求以及保持代码尽可能简单的愿望比 EventEmitter 假定的稍微高效的行为更重要。