Home > OS >  Proper way to use Angular EventEmitter
Proper way to use Angular EventEmitter

Time:06-30

In my simplified scenario there is a parent component, which contains all the business logic.

The parent creates a child component which is only responsible to display a list of buttons.

I want to handle the click events for such buttons in the parent, but, using the suggested EventEmitter pattern I'm forced to use a useless switch case in the parent, in order to detect which button is pressed.

It would be much easier to directly pass the callbacks as input of the child, but I think it's not a good practice. Also these callbacks, as far as I can see, are executed in the context of the child, which is not the desired outcome.

Am I missing something? Is there a better way to achieve my needs? Or I'm completely approaching the problem in a wrong way?

The parent:

import { Component } from "@angular/core";

@Component({
  selector: "parent",
  template: ` <child
      [buttons]="buttons"
      (click)="onButtonClick($event)"
    ></child>
    <span>{{ description }}</span>`
})
export class ParentComponent {
  description = "";
  buttons = ["foo", "bar", "baz"];

  onButtonClick(button: string) {
    switch (button) {
      case "foo":
        this.description = "Calling foo()";
        this.foo();
        break;
      case "bar":
        this.description = "Calling bar()";
        this.bar();
        break;
      case "baz":
        this.description = "Calling baz()";
        this.baz();
        break;
    }
  }

  foo() {}
  bar() {}
  baz() {}
}

The child:

import { Component, EventEmitter, Input, Output } from "@angular/core";

@Component({
  selector: "child",
  template: ` <span *ngFor="let button of buttons">
    <button (click)="onClick(button)">{{ button }}</button>
  </span>`
})
export class ChildComponent {
  @Input() buttons: string[];

  @Output() click = new EventEmitter<string>();

  onClick(button: string) {
    this.click.emit(button);
  }
}

And a working sandbox.

CodePudding user response:

You could think about providing the callbacks within an object array instead of the string array.

Edited Sandbox: https://codesandbox.io/s/cocky-brahmagupta-ueg4nv

The Parent

import { Component } from "@angular/core";

@Component({
  selector: "parent",
  template: ` <child
      [buttons]="buttons"
    ></child>
    <span>{{ description }}</span>`
})
export class ParentComponent {
  description = "";
  buttons = [
    { name: "foo", callback: () => { this.description = "Calling foo()" } },
    // bar
    // baz
  ];
}

The child

@Component({
  selector: "child",
  template: ` <span *ngFor="let button of buttons">
    <button (click)="button.callback()">{{ button.name }}</button>
  </span>`
})
export class ChildComponent {
  @Input() buttons: MyButton[];
}

The interface

interface MyButton {
  name: string,
  callback: () => void;
}

CodePudding user response:

You can emit what-ever

//parent

@Component({
  selector: "parent",
  template: ` <child
      [buttons]="buttons" (click)="$event()"
    ></child>
    <span>{{ description }}</span>`
})
export class ParentComponent {
  description = "";
  buttons = [
    { name: "foo", callback: this.foo},
    { name: "bar", callback: this.bar},
    { name: "baz", callback: this.baz},
  ];
  foo(){}
  bar(){}
  baz(){}
}

//child

@Component({
  selector: "child",
  template: ` <span *ngFor="let button of buttons">
    <button (click)="onClick(button)">{{ button.name }}</button>
  </span>`
})
export class ChildComponent {
  @Input() buttons: any[];

  @Output() click = new EventEmitter<any>();

  onClick(button: any) {
    this.click.emit(button.callback);
  }
}

Update Sound "bizarro" pass a function and execute using function(), but we need think that a function is only an object.

Imagine you define two functions

function1(){console.log("I'm function 1")}
function2(){console.log("I'm function 1")}

In an event click we call to a function

click()
{
   const mainFunction=this.function1
   mainFunction() //execute the function1
}

or according a variable

click()
{
   const mainFunction=this.condicion?this.function1:this.function2
   mainFunction() //execute the function1 or the function2 
                  //if condition is true or false
}

The variables,object,arrays,functions... are physical stored in a memory position. the "name" of the variable, object, function... are only a "pointer" to this memory.

If we want, we could in the answer write

    <child [buttons]="buttons" (click)="executeFunction($event)"></child>

   executeFunction(mainFunction:any)
   {
         mainFunction()
   }

If we want we can type the function to give more clearity if all our function has no argument and no return anything, instead use this uggly "any" use

   executeFunction(mainFunction:()=>void)
   {
         mainFunction()
   }

And

@Output() click = new EventEmitter<()=>void>();
  • Related