18

Parent template:

<ul>
   <tree-item [model]="tree" (addChild)="addChild($event)"></tree-item>
</ul>

Tree item template:

<li>
  <div>{{model.name}}
    <span [hidden]="!isFolder" (click)="addChild.emit(model)">Add Child</span>
  </div>
  <ul *ngFor="let m of model.children">
    <tree-item [model]="m"></tree-item>
  </ul>
</li>

For above example, parent receives addChild event only from the root tree-item (immediate child). Is it possible to bubble up addChild event from any tree-item? I am using angular 2.0.0-rc.0.

Mario G.
  • 671
  • 1
  • 6
  • 10

3 Answers3

18

Events from EventEmitter don't support bubbling.

You can either use element.dispatchEvent() to fire a DOM event that bubbles, or use a shared service like a message bus.

See also https://angular.io/docs/ts/latest/cookbook/component-communication.html

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Could a JavaScript CustomEvent object be used? Is there a con to the pro of this? It's not really covered in the Angular documentation. Maybe because they advise against accessing the DOM directly – Drenai Mar 11 '17 at 20:48
  • That's what `element.dispatchEvent()` is for. Angular suggests to use a shared service instead of DOM events. A DOM event can cause change detection to run for the whole app, while a service with an observable only causes change detection to run for the component that subscribes to the observable. – Günter Zöchbauer Mar 12 '17 at 16:09
  • @GünterZöchbauer let's suppose we have an application where the right click on anchors of a specific class/selector should display a custom context menu. In jQuery, we would have something like `$('body').on('contextmenu', 'a.context', myFunction)` I would rather bind the event to the body instead of the a tag. You are saying that this approach is not possible in Angular2? – igasparetto Mar 13 '17 at 07:58
  • If you listen to a bubbling DOM event, it doesn't matter where you listen to it, it has the same effect everywhere. If you use a custom event that you fire in your code, a shared service with an observable is usually the better option. – Günter Zöchbauer Mar 13 '17 at 08:01
  • Of course there are differences. What I meant is from a performance perspective (in regard change detection). – Günter Zöchbauer Mar 13 '17 at 08:04
  • Do you have an example of `dispatchEvent()` anywhere? I looked at the modules source in my project and they are all using the native `new Event`. I was expecting Angular to have similar functionality than jQuery in that area: `.on`, `.dispatch`, which are extremely useful. – igasparetto Mar 13 '17 at 08:07
  • 1
    Angular doesn't have anything here. Just use plain TS/JS. http://stackoverflow.com/a/42064469/217408. If you want to keep your code platform-neutral (web worker, universal, ... you can use the renderer http://stackoverflow.com/questions/36342890/in-angular2-how-to-know-when-any-form-input-field-lost-focus/36348311#36348311) – Günter Zöchbauer Mar 13 '17 at 08:09
  • This shouldn't be flagged as the answer as it is not giving a solution or even answering the question. It is simply replying with 'EventEmitter doesn't support bubbling', but the question was is it possible to bubble events up from nested child components - not whether EventEmitter supports bubbling. Dereks answer below gives a valid solution to this problem which doesn't require additional 'services' or messing around with the DOM. – padigan Aug 03 '18 at 13:35
  • How does `element.dispatchEvent()` not answer the question? DOM events bubble and you can listen to them like normal Angular events. Also Angular doesn't bubble events because it is considered a bad idea for performance reasons and there are better way to communicate between components like shared services with Observables. The linked page goes into details. – Günter Zöchbauer Aug 03 '18 at 14:15
9

I came here looking for a solution, and I figured out one on my own. On your tree-item, you can add an event handler like so:

<li>
  <div>{{model.name}}
    <span [hidden]="!isFolder" (click)="addChild.emit(model)">Add Child</span>
  </div>
  <ul *ngFor="let m of model.children">
    <tree-item [model]="m" (yourEventEmitter)="handleEventBubble($event)"></tree-item>
  </ul>
</li>

and in your component.ts:

handleEventBubble(event): void {
  this.yourEventEmitter.emit(event);
}

This will cause the event to emit on the parent element, which passes up the chain all the way up to the root element. You can even define custom bubbling logic by transforming the event before re-emitting it.

Derek
  • 103
  • 1
  • 4
0

The closest thing to bubbling the Angular way IMO is using in-place service created by parent and accessed by components as referenced in accepted answer. See Mission Control example.

TL;DR:

  • Create a service providing event observable source
  • inject and provide the service in parent
  • inject the service in child components of the parent - they will access the same instance
  • subscribe to events in parent, emit them in children

The components need not to be direct children of the parent like in the example - the service is accessed deep in hierarchy.

tequilacat
  • 657
  • 1
  • 8
  • 19