Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 4 trigger custom event - EventEmitter vs dispatchEvent()

I'm building directive which should add class when element entered viewport and will also trigger custom event. I found 2 approaches to trigger the event - EventEmitterand dispatchEvent(), both works fine. Which should be used in this case and why? (Any other advices on the code appreciated)

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()">
like image 395
godblessstrawberry Avatar asked Mar 09 '18 13:03

godblessstrawberry


People also ask

Is dispatchEvent synchronous?

Unlike "native" events, which are fired by the browser and invoke event handlers asynchronously via the event loop, dispatchEvent() invokes event handlers synchronously.

Why do we use event emitter in Angular?

🎊 Event Emitters in Angular 🎊 Data flows into your component via property bindings and flows out of your component through event bindings. If you want your component to notify his parent about something you can use the Output decorator with EventEmitter to create a custom event.

Which method is used to publish your own custom event?

Publishing custom events To publish custom events, we will need an instance of ApplicationEventPublisher and then call the method ApplicationEventPublisher#publishEvent(..) . Another way to publish event is to use ApplicationContext#publishEvent(....) .

What is custom event in Angular?

Custom events are created in Angular using its EventEmitter class. These events are used to communicate to the Parent Component from a child Component.

What is EventEmitter in angular?

EventEmitter EventEmitter is a class in angular framework. It has emit () method that emits custom events. We can use EventEmitter in custom event binding.

How do I trigger an event in angular debugelement?

The Angular DebugElement instance provides a handy method for triggering events — triggerEventHandler (). Let’s see how we can use it. We have a simple test for a component that, upon a click, sets an emoji.

What is the triggereventhandler () method in angular?

Three important facts about the triggerEventHandler () method: It will invoke the event handler only if it was declared on the native element by using Angular event bindings, the @HostListener () or @Output decorators (and the less used Renderer.listen () ). For example:

How to use EventEmitter in Custom Event binding?

EventEmitter is a class in angular framework. It has emit () method that emits custom events. We can use EventEmitter in custom event binding. To achieve it first we need to import it in our component file as given below.


2 Answers

EventEmitter is used for @Output()s that can be used for Angular event binding

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

dispatchEvent() fires a DOM event, that also can be bound to like shown for the Angular @Output() event, but can also bubble up the DOM tree.

The former is specific to Angular and for the intented use cases more efficient, the later behaves like other DOM events and can also be listened to by non-Angular code, but might be less efficient.

like image 174
Günter Zöchbauer Avatar answered Oct 16 '22 19:10

Günter Zöchbauer


A combined solution is also possible. Consider a child component that has an EventEmitter for deletion requests:

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

The child component listens for keyboard events to emit GridElementDeleteRequestEvent itself and these being picked up by the parent component.

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

A parent component subscribes to it:

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

Where the handler has the following implementation:

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

In the child there is a deeper nested structure of inner components. One of these inner components is a context sensitive menu that also offers the possibility to delete the so-called GridElement (child in this story).

To prevent a cumbersome architecture tying all EventEmitters from nested components to each other the context sensitive menu dispatches a "regular" DOM Event like this:

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

this.nativeElement.dispatchEvent(event);

To account for this the parent component's handler only has to be decorated with the HostListener directive and the incoming event is checked upon Type (instanceof) and when it is a CustomEvent the detail is casted to a GridElementDeleteRequestEvent like this:

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

  // code
  ...

With this approach both direct (EventEmitter) and indirect (DOM dispatched) events are handled within one event handler on the parent.

Note

Off course this raises the question if the EventEmitter at the child shouldn't be removed completely and the child's keyboard event handler should also just dispatch a DOM Event like this:

@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);
}

This would make the implementation more simple and account for identical Events coming in from different "places" (multiple nested elements at different levels).

A tiny argument to keep the "direct" EventEmitter (in this particular case) might be taken from @Günter Zöchbauer's answer where the EventEmitter is supposed to be (slightly) more efficent. The use case where this answer originates from doesn't involve dozens of GridElementDeletRequestEvent's so keeping the EventEmitter will have negligible effect.

The requirement for having the GridElementDeleteRequestEvent being fired from multiple Angular components and the desire to keep the code as simple as possible weigh heavier than a supposed slightly more efficient behavior of the EventEmitter.

like image 3
Bernoulli IT Avatar answered Oct 16 '22 21:10

Bernoulli IT