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

时间:2018-03-09 13:05:29

标签: angular eventemitter

我正在构建指令,该指令应该在元素输入视口时添加类,并且还将触发自定义事件。我找到了两种触发事件的方法 - 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()">

2 个答案:

答案 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的假设稍微更有效的行为更重要。