I am trying to implement something like a delegation pattern in Angular. When the user clicks on a nav-item, I would like to call a function which then emits an event which should in turn be handled by some other component listening for the event.

Here is the scenario: I have a Navigation component:

import {Component, Output, EventEmitter} from 'angular2/core';

@Component({
    // other properties left out for brevity
    events : ['navchange'], 
    template:`
      <div class="nav-item" (click)="selectedNavItem(1)"></div>
    `
})

export class Navigation {

    @Output() navchange: EventEmitter<number> = new EventEmitter();

    selectedNavItem(item: number) {
        console.log('selected nav item ' + item);
        this.navchange.emit(item)
    }

}

Here is the observing component:

export class ObservingComponent {

  // How do I observe the event ? 
  // <----------Observe/Register Event ?-------->

  public selectedNavItem(item: number) {
    console.log('item index changed!');
  }

}

The key question is, how do I make the observing component observe the event in question ?

6 Answers 11

You need to use the Navigation component in the template of ObservingComponent ( dont't forget to add a selector to Navigation component .. navigation-component for ex )

<navigation-component (navchange)='onNavGhange($event)'></navigation-component>

And implement onNavGhange() in ObservingComponent

onNavGhange(event) {
  console.log(event);
}

Last thing .. you don't need the events attribute in @Componennt

events : ['navchange'], 
upvote
  flag
This only hooks up an event for the underlying component. That's not what I am trying to do. I could have just said something like (^navchange) (the caret is for event bubbling) on the nav-item but I just want to emit an event that others can observe. – the_critic
upvote
  flag
you can use navchange.toRx().subscribe() .. but you will need to have a reference on navchange on ObservingComponent – Mourad Zouabi

Breaking news: I've added another answer that uses an Observable rather than an EventEmitter. I recommend that answer over this one. And actually, using an EventEmitter in a service is bad practice.


Original answer: (don't do this)

Put the EventEmitter into a service, which allows the ObservingComponent to directly subscribe (and unsubscribe) to the event:

import {EventEmitter} from 'angular2/core';

export class NavService {
  navchange: EventEmitter<number> = new EventEmitter();
  constructor() {}
  emit(number) {
    this.navchange.emit(number);
  }
  subscribe(component, callback) {
    // set 'this' to component when callback is called
    return this.navchange.subscribe(data => call.callback(component, data));
  }
}

@Component({
  selector: 'obs-comp',
  template: 'obs component, index: {{index}}'
})
export class ObservingComponent {
  item: number;
  subscription: any;
  constructor(private navService:NavService) {
   this.subscription = this.navService.subscribe(this, this.selectedNavItem);
  }
  selectedNavItem(item: number) {
    console.log('item index changed!', item);
    this.item = item;
  }
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

@Component({
  selector: 'my-nav',
  template:`
    <div class="nav-item" (click)="selectedNavItem(1)">item 1 (click me)</div>
  `,
})
export class Navigation {
  constructor(private navService:NavService) {}
  selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this.navService.emit(item);
  }
}

If you try the Plunker, there are a few things I don't like about this approach:

  • ObservingComponent needs to unsubscribe when it is destroyed
  • we have to pass the component to subscribe() so that the proper this is set when the callback is called

Update: An alternative that solves the 2nd bullet is to have the ObservingComponent directly subscribe to the navchange EventEmitter property:

constructor(private navService:NavService) {
   this.subscription = this.navService.navchange.subscribe(data =>
     this.selectedNavItem(data));
}

If we subscribe directly, then we wouldn't need the subscribe() method on the NavService.

To make the NavService slightly more encapsulated, you could add a getNavChangeEmitter() method and use that:

getNavChangeEmitter() { return this.navchange; }  // in NavService

constructor(private navService:NavService) {  // in ObservingComponent
   this.subscription = this.navService.getNavChangeEmitter().subscribe(data =>
     this.selectedNavItem(data));
}
upvote
  flag
I prefer this solution to the answer provided by Mr Zouabi, but I am not a fan of this solution either, to be honest. I don't care about unsubscribing on destruction, but I do hate the fact that we have to pass the component to subscribe to the event... – the_critic
upvote
  flag
I actually thought about this and decided to go with this solution. I'd love to have a slightly cleaner solution, but I'm not sure it's possible (or I'm probably not able to come up with something that's more elegant I should say). – the_critic
1 upvote
  flag
@the_critic, I thought of another way, see the Update. – Mark Rajcok
1 upvote
  flag
Yes, yes and yes! I love it! Thank you so much! – the_critic
upvote
  flag
actually the 2nd bullet problem is that a reference to the Function is being passed instead. to fix: this.subscription = this.navService.subscribe(() => this.selectedNavItem()); and on subscribe: return this.navchange.subscribe(callback); – André Werlang
up vote 296 down vote accepted

Update 2016-06-27: instead of using Observables, use either

  • a BehaviorSubject, as recommended by @Abdulrahman in a comment, or
  • a ReplaySubject, as recommended by @Jason Goemaat in a comment

A Subject is both an Observable (so we can subscribe() to it) and an Observer (so we can call next() on it to emit a new value). We exploit this feature. A Subject allows values to be multicast to many Observers. We don't exploit this feature (we only have one Observer).

BehaviorSubject is a variant of Subject. It has the notion of "the current value". We exploit this: whenever we create an ObservingComponent, it gets the current navigation item value from the BehaviorSubject automatically.

The code below and the plunker use BehaviorSubject.

ReplaySubject is another variant of Subject. If you want to wait until a value is actually produced, use ReplaySubject(1). Whereas a BehaviorSubject requires an initial value (which will be provided immediately), ReplaySubject does not. ReplaySubject will always provide the most recent value, but since it does not have a required initial value, the service can do some async operation before returning it's first value. It will still fire immediately on subsequent calls with the most recent value. If you just want one value, use first() on the subscription. You do not have to unsubscribe if you use first().

import {Injectable}      from '@angular/core'
import {BehaviorSubject} from 'rxjs/BehaviorSubject';

@Injectable()
export class NavService {
  // Observable navItem source
  private _navItemSource = new BehaviorSubject<number>(0);
  // Observable navItem stream
  navItem$ = this._navItemSource.asObservable();
  // service command
  changeNav(number) {
    this._navItemSource.next(number);
  }
}
import {Component}    from '@angular/core';
import {NavService}   from './nav.service';
import {Subscription} from 'rxjs/Subscription';

@Component({
  selector: 'obs-comp',
  template: `obs component, item: {{item}}`
})
export class ObservingComponent {
  item: number;
  subscription:Subscription;
  constructor(private _navService:NavService) {}
  ngOnInit() {
    this.subscription = this._navService.navItem$
       .subscribe(item => this.item = item)
  }
  ngOnDestroy() {
    // prevent memory leak when component is destroyed
    this.subscription.unsubscribe();
  }
}
@Component({
  selector: 'my-nav',
  template:`
    <div class="nav-item" (click)="selectedNavItem(1)">nav 1 (click me)</div>
    <div class="nav-item" (click)="selectedNavItem(2)">nav 2 (click me)</div>`
})
export class Navigation {
  item = 1;
  constructor(private _navService:NavService) {}
  selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this._navService.changeNav(item);
  }
}

Plunker


Original answer that uses an Observable: (it requires more code and logic than using a BehaviorSubject, so I don't recommend it, but it may be instructive)

So, here's an implementation that uses an Observable instead of an EventEmitter. Unlike my EventEmitter implementation, this implementation also stores the currently selected navItem in the service, so that when an observing component is created, it can retrieve the current value via API call navItem(), and then be notified of changes via the navChange$ Observable.

import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/share';
import {Observer} from 'rxjs/Observer';

export class NavService {
  private _navItem = 0;
  navChange$: Observable<number>;
  private _observer: Observer;
  constructor() {
    this.navChange$ = new Observable(observer =>
      this._observer = observer).share();
    // share() allows multiple subscribers
  }
  changeNav(number) {
    this._navItem = number;
    this._observer.next(number);
  }
  navItem() {
    return this._navItem;
  }
}

@Component({
  selector: 'obs-comp',
  template: `obs component, item: {{item}}`
})
export class ObservingComponent {
  item: number;
  subscription: any;
  constructor(private _navService:NavService) {}
  ngOnInit() {
    this.item = this._navService.navItem();
    this.subscription = this._navService.navChange$.subscribe(
      item => this.selectedNavItem(item));
  }
  selectedNavItem(item: number) {
    this.item = item;
  }
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

@Component({
  selector: 'my-nav',
  template:`
    <div class="nav-item" (click)="selectedNavItem(1)">nav 1 (click me)</div>
    <div class="nav-item" (click)="selectedNavItem(2)">nav 2 (click me)</div>
  `,
})
export class Navigation {
  item:number;
  constructor(private _navService:NavService) {}
  selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this._navService.changeNav(item);
  }
}

Plunker


See also the Component Interaction Cookbook example, which uses a Subject in addition to observables. Although the example is "parent and children communication," the same technique is applicable for unrelated components.

2 upvote
  flag
It was mentioned repeatedly in comments to Angular2 issues that it's discouraged to use EventEmitter anywhere except for outputs. They are currently rewriting the tutorials (server communication AFAIR) to not encourage this practice. – Günter Zöchbauer
1 upvote
  flag
@GünterZöchbauer, thanks for the info. I can't keep up with SO, much less github issues. – Mark Rajcok
1 upvote
  flag
It's really hard too keep up with all the changes in Angular2. I just tried to strengthen your point :) – Günter Zöchbauer
upvote
  flag
@MarkRajcok one quick question.. how does change detection engine know that change has happened on observable object? can you explain that please OR provide me a reference link, I'll go through it. Thanks :) – Pankaj Parkar
upvote
  flag
I haven't had the time to verify/try/look (at) this, but I've ticked it for the sake of visibility, I will try that at some point. Thanks for your effort Mark. – the_critic
upvote
  flag
I really like this answer and it calls into question to me the 'deeper' answer with things like Flux (blog.jhades.org/…). I think this approach, coupled with events that can be quickly 'filtered', should allow performant global 'event bus' type behavior without the extra engineering (or horrible 'reduce' function using marker classes). – JimB
upvote
  flag
So I love this answer and I tried it and works like a charm. I do have a question on how would you best manage a lot of changes going on the UI. Would you create a separate observable service, like your example, for each one? Or would you just create one observable service that has an object that tracks all changes? – Marin Petkov
upvote
  flag
Here is the plnkr to what I was talking about in previous comment: plnkr.co/edit/2s0PaKBVTCzI5iBvZylU?p=preview – Marin Petkov
1 upvote
  flag
@MarinPetkov, "it depends". For app-wide events, I'd probably create one service with multiple observables. For the rest, I would first think about what services I want in my app, then I would add observables to those services that needed them. I would not architect from the other direction, i.e., start with the events, and then create services based on events. – Mark Rajcok
2 upvote
  flag
Is there a way initialize the service and fire off a first event from within the Navigation component in the sample code above? The problem is that the _observer of the service object is at least not initialized at the time of ngOnInit() of the Navigation component being called. – ComFreek
upvote
  flag
@ComFreek, yes, just wrap the changeNav() call in a setTimeout() to give the service a chance to create the _observer: ngOnInit() { setTimeout(_ => this._navService.changeNav(3)); } – Mark Rajcok
3 upvote
  flag
May I suggest using BehaviorSubject instead of Observable. It's closer to EventEmitter because It's "hot" meaning it's already "shared", it's designed to save the current value and finally it implements both Observable and Observer which will save you at least five lines of code and two properties – Abdulrahman
upvote
  flag
@Abdulrahman, great suggestion. I'll try to update my answer sometime in the next few days (and use RC.1). – Mark Rajcok
2 upvote
  flag
@PankajParkar, regarding "how does change detection engine know that change has happened on observable object" -- I deleted my previous comment response. I learned recently that Angular does not monkey-patch subscribe(), so it can not detect when an observable changes. Normally, there is some async event that fires (in my sample code, it is the button click events), and the associated callback will call next() on an observable. But change detection runs because of the async event, not because of the observable change. See also Günter comments: //allinonescript.com/a/36846501/215945 – Mark Rajcok
upvote
  flag
@MarkRajcok thanks mark for correcting old comment, and sharing true information – Pankaj Parkar
8 upvote
  flag
If you want to wait until a value is actually produced you can use ReplaySubject(1). A BehaviorSubject requires an initial value which will be provided immediatly. The ReplaySubject(1) will always provide the most recent value, but does not have an initial value required so the service can do some async operation before returning it's first value, but still fire immediately on subsequent calls with the last value. If you're just interested in one value you can use first() on the subscription and not have to unsubscribe at the end because that will complete. – Jason Goemaat
upvote
  flag
Thanks @JasonGoemaat, I added your comment into the answer. – Mark Rajcok
upvote
  flag
Wondering, Used on a service that inserts a form into database and was supposed to return the id created. ngOnInit() { console.log('Log1'); this.createdRowSubscription = this.donorService.createdRowObservable$.subscribe( item => { console.log('Log2',item); this.selectRow(item); //makes it the selected row, loading the id (among the rest of data) from the store } ); }. Apparently it works and does get called (because if I comment ngOnInit the form stops selecting the created row). However, Log2's line is never printed. ever. Log1 gets printed on Init as expected. – gia
upvote
  flag
@harunurhan, you might be inadvertently creating multiple instances of the server. Create a plunker and post a new question for help. – Mark Rajcok
upvote
  flag
@MarkRajcok regarding your comment starting with "it depends". Wouldn't having a service with everything and injected in a bunch of component perform worse ? – Ced
upvote
  flag
asObservable() doesn't seem to be required for this to work. Am I wrong? – Stephen Paul
1 upvote
  flag
any tutorial or article about this solution ? thanks for the answer :) – Francis Manoj Fernnado
upvote
  flag
@MarkRajcok love this example with the exception that both Navigation and ObservingComponent need to rely on NavService. How would I go about making ObservingComponent dumb so that the data I need to observe could be passed in via an @Input()? – GFoley83
upvote
  flag
Never mind. Answer was in my own question. Passing the navItem$ observable in as an @Input() and using the async pipe means all logic can be removed from ObservingComponent making it completely dumb. Plunker example – GFoley83
upvote
  flag
Is this solution work for Angular 2.4? – Maxime Gélinas
upvote
  flag
I made a video using this solution to send a search query from a NavBar component to a un-related Results component page. youtube.com/watch?v=k8hMfoNIo4Y – venturz909
upvote
  flag
Note: If NavService is a singleton service provided to multiple components, make sure that NavService is NOT in the providers array of any subscribed components. This will result in multiple instances of NavService and the subscribers won't update correctly. Maybe that's obvious, but thought I'd share in case others encounter a similar problem. – Stevethemacguy
upvote
  flag
useless, it gets back to zero when page is refreshed – Diego
1 upvote
  flag
@Diego What do you expect? Refreshing the page is closing the app (ie. dumping everything from memory) and restarting the app from scratch. So everything gets reinitialized, that's why it's zero again. If you don't want it to be zero at the start, then you need to read an initial value from storage (either local or from a server) – rmcsharry

I found out another solution for this case without using Reactivex neither services. I actually love the rxjx API however I think it goes best when resolving an async and/or complex function. Using It in that way, Its pretty exceeded to me.

What I think you are looking for is for a broadcast. Just that. And I found out this solution:

<app>
  <app-nav (selectedTab)="onSelectedTab($event)"></app-nav>
       // This component bellow wants to know when a tab is selected
       // broadcast here is a property of app component
  <app-interested [broadcast]="broadcast"></app-interested>
</app>

 @Component class App {
   broadcast: EventEmitter<tab>;

   constructor() {
     this.broadcast = new EventEmitter<tab>();
   }

   onSelectedTab(tab) {
     this.broadcast.emit(tab)
   }    
 }

 @Component class AppInterestedComponent implements OnInit {
   broadcast: EventEmitter<Tab>();

   doSomethingWhenTab(tab){ 
      ...
    }     

   ngOnInit() {
     this.broadcast.subscribe((tab) => this.doSomethingWhenTab(tab))
   }
 }

This is a full working example: https://plnkr.co/edit/xGVuFBOpk2GP0pRBImsE

upvote
  flag
Angular docs recommend that you never use .subscribe() – Porschiey
1 upvote
  flag
Look at the best answer, it uses subscribe method too.. Actually nowday I would recommend using Redux or some other state control for solving this communication problem between components. It is infinity much better than any other solution though it add extra complexity. Either using Angular 2 components event handler sintax or explicitly using subscribe method the concept remains the same. My final thoughts are if you want an definitive solution for that problem use Redux, otherwise use services with event emitter. – Nicholas Marcaccini Augusto
upvote
  flag
subscribe is valid as long as angular does not remove the fact that it's observable. .subscribe() is used in the best answer, but not on that particular object. – Porschiey

If one wants to follow a more Reactive oriented style of programming, then definitely the concept of "Everything is a stream" comes into picture and hence, use Observables to deal with these streams as often as possible.

you can use BehaviourSubject as described above or there is one more way:

you can handle EventEmitter like this: first add a selector

import {Component, Output, EventEmitter} from 'angular2/core';

@Component({
// other properties left out for brevity
selector: 'app-nav-component', //declaring selector
template:`
  <div class="nav-item" (click)="selectedNavItem(1)"></div>
`
 })

 export class Navigation {

@Output() navchange: EventEmitter<number> = new EventEmitter();

selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this.navchange.emit(item)
}

}

Now you can handle this event like let us suppose observer.component.html is the view of Observer component

<app-nav-component (navchange)="recieveIdFromNav($event)"></app-nav-component>

then in the ObservingComponent.ts

export class ObservingComponent {

 //method to recieve the value from nav component

 public recieveIdFromNav(id: number) {
   console.log('here is the id sent from nav component ', id);
 }

 }

Not the answer you're looking for? Browse other questions tagged or ask your own question.