Angular 4 触发自定义事件 - EventEmitter 与 dispatchEvent()
Angular 4 trigger custom event - EventEmitter vs dispatchEvent()
我正在构建指令,该指令应在元素进入视口时添加 class,并且还会触发自定义事件。我找到了 2 种触发事件的方法 - 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()">
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 假定的稍微高效的行为更重要。
我正在构建指令,该指令应在元素进入视口时添加 class,并且还会触发自定义事件。我找到了 2 种触发事件的方法 - 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()">
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 假定的稍微高效的行为更重要。