2

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.

3
  • Could you provide a less abstract example that illustrates the actual problem? Commented May 5, 2017 at 19:58
  • Why don't you create an extendable component which can allow you to define the count of replication and an array list of functions to bind? Commented May 5, 2017 at 20:06
  • @jonrsharpe I've updated my answer Commented May 5, 2017 at 22:11

3 Answers 3

1

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Just like wrote to Aravindin his answer. I did what you've sugested too and it didn't worked either. As far as I know for the componentXCustomEvent to trigger anything, that event must me on an element in the template. And since it never appears in the template, it's never fired. The only event in the templatem in my code and in your answer id customEvent.
Ok, I see what you're trying to do, but unfortunately, it wont work since what you put in the menuCustomEvent on the menu element is static. I don't want it to be only one thing, I want it to be any of the menu-elem' s customEvent. If you take a look a each menu-elem they each have a different function to be called when customEvent is triggered.
Well, that was my point about if you need to distinguish which menu-elem got clicked within your sub-menu, then you need to add some additional information to your sub-menu $event. This info will then be passed through your menuCustomEvent.emit, and the top level component can have a single event handler that switches on that data and calls the different superCoolEvent1, 2 or 3 event handlers.
Ok but if I want it to be all in the template, do you know if it's possible?
well, if i'm understanding you properly, then if you want your sub-menu to surface different events depending on what internal menu elem is clicked, you can create separate output event emitters for each menu elem in your sub menu, and then separate output event emitters for your menu. That way, you can bind your different event handlers in your top-level component to different events on your menu. That's one approach that you can take, if you want everything visible in the template.
0

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
}

2 Comments

I'm not sure how reallyCoolEvent($event) will be called, but still I've tried what you suggested and it didn't worked. Can you give me a little more details please.
Sure. Can you create a plunker
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.