16

One can use Output() decorator to emit the event from the child component so that the parent component can catch it. What I need is to catch an event emitted by any child component, which is not necessarily a direct child of a given component.

Let ComponentTwo be a component which emits the event. If the app has the following structure: App -> ComponentTwo, then we can easily catch the event emitted by ComponentTwo. But if we consider structure like App -> ComponentOne -> ComponentTwo, then we can't catch the emitter directly.

Here is a plunker that illustrates the problem.

So is there a way to sort of propagate event to all parent components? Thanks.

nakajuice
  • 691
  • 1
  • 11
  • 23
  • Here's a different approach using `CustomEvents` rather than `Outputs()` https://stackblitz.com/edit/angular-5wv8p5 Details here https://stackoverflow.com/a/68784117/1277159 :) – WSD Aug 14 '21 at 14:20

3 Answers3

26

There is no support of bubbling for such events. You need to manually catch them and emit to the parent.

Another approach would be to leverage a shared service for this tree of components and use an observable / subject in it.

This way all components can subscribe on it to be notified of events even within sub sub children.

constructor(private service: SharedService) {
  this.service.notifier$.subscribe(
    data => {
      (...)
    });
}

The event will be triggered this way:

constructor(private service: SharedService) {
}

notify() {
  this.service.notifier$.next('some data');
}

See this link for more details:

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • 1
    Thank you. BTW is absence of bubbling due to design of A2 or just not implemented (yet)? – nakajuice May 06 '16 at 13:28
  • 1
    The direct link has changed. It is now: https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service. – Carlos Garcia Sep 03 '20 at 20:16
1

If you find you have to pass certain events through make sure to be consistent and come up with a naming strategy. For instance I like to add raise to the beginning of the method name if the only thing that method does is to re-raise an event.

eg.

(paypalTokenized)="raisePaypalTokenized($event)"

and in the ts file:

@Output('paypalTokenized')
public paypalTokenizedEmitter = new EventEmitter<PayPalTokenizedPayload>();

// PayPal tokenized
public raisePaypalTokenized(payload: PayPalTokenizedPayload)
{
    // no other logic here!
    this.paypalTokenizedEmitter.emit(payload);
}

This means I can tell immediately that the event is being passed through with no further action.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
-1

the thierry answer is the correct aproach, but if you want to avoid use shared service, you can do somthing like this

view.html

<componentA
   (actionA)=actionHandler($event)
>
</componentA>

view.ts

actionHandler(event){
  //doSomething
}

componentA.html

<componentB
   (actionB)=actionHandlerA($event)
>
</componentB>

componentA.ts

@Output() actionA: EventEmitter<{}> = new EventEmitter();

constructor() {
}

actionHandlerA(event){
  this.actionA.emit(event);
}

componentB.html

<a (click)="actionHandlerB(...)" >click me</a>

componentB.ts

@Output() actionB: EventEmitter<{}> = new EventEmitter();

constructor() {
}

actionHandlerB(o: objectModel){
  this.actionB.emit(new objectModel(o));
}