18
votes

If I have multiple levels of angular components, how can I use @Output to event emit an action from child to the grand parent?

Grandparent:

<parent (handleClick)="grandmaHandleClick($event)">
<parent>
...
grandmaHandleClick(event) {
  console.log('grandma knows you clicked')
}

Parent:

<child (handleClick)="handleClick($event)">
</child>

Child:

<div (click)="onClick()">Click button
</div>
...
@Output() handleClick = new EventEmitter
onClick() {
  this.handleClick.emit('clicked a button')
}

I am trying to have it so that @Output can prop drill a few components deep, whats the best way to accomplish this, and can you provide example?

3
Just use a service and Subject/BehaviorSubject instead. blog.angular-university.io/…baao

3 Answers

24
votes

There could be 2 ways:

  1. Using @output:

Grandparent

<parent (notifyGrandParent)="grandmaHandleClick($event)">
<parent>
...
grandmaHandleClick(event) {
  console.log('grandma knows you clicked')
}

Parent:

<child (handleClick)="childEvent($event)">
</child>

@Output() notifyGrandParent= new EventEmitter();
childEvent(event) {
  this.notifyGrandParent.emit('event')
}

Child is implemented properly in the code so it is good to go.

  1. Using BehaviorSubject via Service: With this much level of nesting, you can actually create some service like EventService, and then create BehaviorSubject which can directly be subscribed by the GrandParent. Also, to make this service more component specific, you can keep this service in a module which will have other 3 components (GrandParent, Parent and Child)
export class EventService{

 private childClickedEvent = new BehaviorSubject<string>('');

  emitChildEvent(msg: string){
     this.childClickedEvent.next(msg)
  }

  childEventListner(){
     return this.childClickedEvent.asObservable();
   } 

}

and then in components:

ChildComponent

export class ChildComponent{
   constructor(private evtSvc: EventService){}

   onClick(){
     this.evtSvc.emitChildEvent('clicked a button')
   }
}

GrandParent

export class GrandComponent{
   constructor(private evtSvc: EventService){}

   ngOnInit(){
     this.evtSvc.childEventListner().subscribe(info =>{
         console.log(info); // here you get the message from Child component
      })
   }
}

Please note that, with @output event, you create a tight coupling of components and so a strong dependency (parent-child-grandchild) is created. If the component are not reusable and are only created to serve this purpose, then @output will also make sense because it'll convey the message to any new developer that they have parent-child relationship.

Creating a service to pass data also exposes the data to other components which can inject service in constructor.

So, the decision should be taken accordingly.

3
votes

Use rxjs/subject it can be observer and observable in same time

Usage:

1.Create Subject Property in service:

import { Subject } from 'rxjs';

export class AuthService {
   loginAccures: Subject<boolean> = new Subject<boolean>();
}

2.When event happend in child page/component use :

logout(){
  this.authService.loginAccures.next(false);
}

3.And subscribe to subject in parent page/component:

constructor(private authService: AuthService) {
    this.authService.loginAccures.subscribe((isLoggedIn: boolean) => {this.isLoggedIn = isLoggedIn;})
}
1
votes

There is a wonderful library called ng-interconnect for this purpose. You can emit an event from any component to any component using this library.

The library provide for creating -

  • Broadcasters - one message point that send multiple events to many receivers

  • Listeners - one message point which listens from multiple connections

let messageStream: IMessageStream = createBroadcaster('home/stateChanged');   //Create a broadcaster```
 
 ...
 ...
 /*Receive from it from another component somewhere in the hierarchy*/
 
 let userReceiver = receiveFrom('home/stateChanged', 'contacts/user', (data, error, complete) => {
  console.log(data);
  console.log(error);
  console.log(complete);
 })
 
 
 '''
 '''
 /*Broadcast messages from the first component*/
 nessageStream.emit('logged-in');