Trigger custom event without eventEmitter in angular2

I have a component with an event on it like this:

<menu-elem (onClick)="reallyCoolEvent($event)"></custom-component>

Right now, i can trigger my reallyCoolEvent with an EventEmitter from my custom-component.

@Output() onClick= new EventEmitter();
this.onClick.emit(stuff);

Like this, in my other component the function reallyCoolEvent will be called and the stuff will be passed to that function. That works really well!

Now, lets say, I want to copy that component for whatever reason and I want to trigger the same event from the original. Right now, I'm going through my list of onClick and if they macth my criteria I store them in a list that I show somewhere else on the page. When I trigger the onClick from there the event is never triggered.

<menu>
  <menu-elem label="Action 1" (onClick)="superCoolEvent($event)"></menu-elem>
  <menu-elem label="Action 2" (onClick)="superCoolEvent2($event)"></menu-elem>
  <menu-elem label="Action 3" (onClick)="superCoolEvent3($event)"></menu-elem>
</menu>

In menu:

template:

<div>
   <ng-content></ng-content>
</div>
<sub-menu *ngFor="let subMenu of subMenus" [style.top]="subMenu.top" [style.left]="subMenu.left" [items]="subMenu.items"></sub-menu>

In sub-menu:

template:

<menu-elem *ngFor="let item of items" [label]="item.label"></menu-elem>

By doing that, I would like to be able to trigger the onClick within the <div class="here"> and still trigger the event from the original template : reallyCoolEvent($event). I don't know if I can do that or not. Of course the onClick is not set because I don't have the data anymore from the template. Or at least, I don't know how to get the data.

The only solution that I have so far, is to duplicate my entries like this:

<menu>
  <menu-elem label="1" (onClick)="superCoolEvent($event)"></menu-elem>
  <menu-elem label="2" (onClick)="superCoolEvent2($event)"></menu-elem>
  <menu-elem label="3" (onClick)="superCoolEvent3($event)"></menu-elem>
  <div class="test">
    <menu-elem label="1" (onClick)="superCoolEvent($event)"></menu-elem>
    <menu-elem label="2" (onClick)="superCoolEvent2($event)"></menu-elem>
    <menu-elem label="3" (onClick)="superCoolEvent3($event)"></menu-elem>
  </div>
</menu>

And depending of my criteria I can show them or not, but I don't like that way since the template gets too big for nothing.

Upvotes: 2

Views: 2983

Answers (3)

snorkpete
snorkpete

Reputation: 14564

Your basic problem is that custom events don't bubble in Angular.

Since your menu looks like this:

<div>
   <ng-content></ng-content>
</div>
<sub-menu *ngFor="let subMenu of subMenus" [items]="subMenu.items"></sub-menu>

Any event triggered within sub-menu in the template above won't automatically be available outside outside of sub-menu.

So, if you want the customEvent from within your sub-menu template to be available outside of sub-menu, you have to manually propagate the event through. Something like:

<div>
   <ng-content></ng-content>
</div>
<div class="here">
  <sub-menu *ngFor="let item of items" label="item.label" (submenuCustomEvent)="menuCustomEvent.emit($event)"></sub-menu>
</div>

where submenuCustomEvent is an EventEmitter property of sub-menu and menuCustomEvent is an EventEmitter property of menu.

EDIT:

That means that your sub-menu has to look something like:

@Component({
  .....
  template: `   
    <menu-elem *ngFor="let item of items" 
         [label]="item.label"
         (customEvent)="submenuCustomEvent.emit($event)
    >
    </menu-elem>
   `
})
export class Submenu {
    .....
   @Output()
   submenuCustomEvent = new EventEmitter();
    ....
}

and your menu becomes:

@Component({
  .....
  template: `   
    <div>
      <ng-content></ng-content>
    </div>
    <sub-menu *ngFor="let subMenu of subMenus" 
       [items]="subMenu.items"
       (submenuCustomEvent)="menuCustomEvent.emit($event)"
    >
    </sub-menu>
   `
})
export class Menu {
    .....
   @Output()
   menuCustomEvent = new EventEmitter();
    ....
}  

So, the template that you're using your menu now looks like:

<menu (menuCustomEvent)="superCoolEvent($event)">
  <menu-elem label="Action 1" (customEvent)="superCoolEvent($event)"></menu-elem>
  <menu-elem label="Action 2" (customEvent)="superCoolEvent2($event)"></menu-elem>
  <menu-elem label="Action 3" (customEvent)="superCoolEvent3($event)"></menu-elem>
</menu>

Points to note:

  • If you need to distinguish which menu-elem nested within sub-menu emitted the event, you might have to add some additional information to the event emitted by submenuCustomEvent.

  • I named the event emitters for menu and sub-menu differently in my example to highlight the difference, but you can use the same event emitter name in your code (i.e. call all the nested event emitters onClick)

  • A service might help in this scenario to avoid the manual wiring up of event emitters through the component hierarchy. Using a service, your top level component that's using menu can be directly notified if a menu-elem within sub-menu is clicked, without having the intermediate event propagation

  • Finally, you are using a custom onClick event. But if you use the browser click event directly, you could avoid the manual propagation since browser click events DO propagate. However, you don't have control over the shape of the emitted event, so there is that limitation.

Upvotes: 1

Julia Passynkova
Julia Passynkova

Reputation: 17879

I am not sure if I understood you fully. However, you can inject a reference of Parent component to child and use parent's eventemiiter.

 constructor(private myParent: Parent) {}

 go() {
    this.myParent.custometEvent.emit(data);
 } 

Upvotes: 0

Aravind
Aravind

Reputation: 41571

Your answer is as below

  <custom-component *ngFor="let item of items" label="item.label" (customEvent)="eventHandler($event)"></custom-component>

The corresponding code should be in component-x ts file

eventHandler(event){
   this.customEvent.emit(..)
}

This will trigger the customEvent() in your custom-component as in this line

<custom-component (customEvent)="reallyCoolEvent($event)"></custom-component>

So this method

reallyCoolEvent(event){
   ///here is your emitted object from component-x
}

Upvotes: 0

Related Questions