131

I know how to raise an event with the EventEmitter. I can also attach a method to be called if I have a component like this:

<component-with-event (myevent)="mymethod($event)" />

When I have a component like this, everything works great. I moved some logic into a service and I need to raise an event from inside the Service. What I did was this:

export class MyService {
  myevent: EventEmitter = new EventEmitter();

  someMethodThatWillRaiseEvent() {
    this.myevent.next({data: 'fun'});
  }
}

I have a component that needs to update some value based on this event but i can't seem to make it work. What I tried was this:

//Annotations...
export class MyComponent {
  constructor(myService: MyService) {
    //myService is injected properly and i already use methods/shared data on this.
    myService.myevent.on(... // 'on' is not a method <-- not working
    myService.myevent.subscribe(.. // subscribe is not a method <-- not working
  }
}

How do i make MyComponent subscribe to the event when the service that raises it is not a component?

I'm on On 2.0.0-alpha.28

EDIT: Modified my "working example" to actually work, so focus can be put on the not-working part ;)

Example code: http://plnkr.co/edit/m1x62WoCHpKtx0uLNsIv

Abrar Jahin
  • 13,970
  • 24
  • 112
  • 161
Per Hornshøj-Schierbeck
  • 15,097
  • 21
  • 80
  • 101
  • 1
    It could be more an issue of design as a service should not emit events on behalf of a component, as that tightly couples the service to the component. for example what if there are multiple instances of the component, should they all emit events in that case? – Angular University Jul 07 '15 at 22:08
  • @jhadesdev I read you. I did redesign the solution so the service no longer needs to emit the result. I still think some designs would bennefit from being able to raise events - depending what kind of "service" it is... – Per Hornshøj-Schierbeck Jul 08 '15 at 06:05
  • one way then could be to have the component create the event emitter instead of the service, and then pass in the event emitter as an argument to the service – Angular University Jul 08 '15 at 06:27

3 Answers3

164

Update: I have found a better/proper way to solve this problem using a BehaviorSubject or an Observable rather than an EventEmitter. Please see this answer: https://stackoverflow.com/a/35568924/215945

Also, the Angular docs now have a cookbook example that uses a Subject.


Original/outdated/wrong answer: again, don't use an EventEmitter in a service. That is an anti-pattern.

Using beta.1... NavService contains the EventEmiter. Component Navigation emits events via the service, and component ObservingComponent subscribes to the events.

nav.service.ts

import {EventEmitter} from 'angular2/core';
export class NavService {
  navchange: EventEmitter<number> = new EventEmitter();
  constructor() {}
  emitNavChangeEvent(number) {
    this.navchange.emit(number);
  }
  getNavChangeEmitter() {
    return this.navchange;
  }
}

components.ts

import {Component} from 'angular2/core';
import {NavService} from '../services/NavService';

@Component({
  selector: 'obs-comp',
  template: `obs component, item: {{item}}`
})
export class ObservingComponent {
  item: number = 0;
  subscription: any;
  constructor(private navService:NavService) {}
  ngOnInit() {
    this.subscription = this.navService.getNavChangeEmitter()
      .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 = 1;
  constructor(private navService:NavService) {}
  selectedNavItem(item: number) {
    console.log('selected nav item ' + item);
    this.navService.emitNavChangeEvent(item);
  }
}

Plunker

Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • I've followed this, but it only works for the class itself. It's not working for another class (from another file). It seems that the problem is in the service class instance. Is there anyway to make the service class become static? – asubanovsky Jan 20 '16 at 11:06
  • 6
    @asubanovsky, in the [plunker](http://plnkr.co/edit/wzN7ZKU1CmVwbtxw8XFJ?p=preview) I only inject NavService at the bootstrap (i.e., root component) level, so there should only be one instance of the service for the entire app. Are you injecting it elsewhere with `providers: [NavService]`? If so, you will get multiple instances of the service. – Mark Rajcok Jan 20 '16 at 15:04
  • Thank you very much. It is now working as my expectation. Now I understand the difference between injecting the providers at the bootstrap level and at the component level. – asubanovsky Jan 21 '16 at 01:36
  • This post was seriously changed to a state where i no longer 'get it'. Why is calling a mehtod on the service .getNavChangeEmitter() that returns the EventEmitter different from directly subscribing to navChange? – Per Hornshøj-Schierbeck Jun 17 '16 at 07:46
  • @PerHornshøj-Schierbeck, I used method getNavChangeEmitter() to hide the fact that an EventEmitter was being used inside the service. Users of the method could assume that the returned object has a `subscribe()` method. By using a method, we can easily change the EventEmitter to a Subject or an Observable, which is much better, since we've now learned that EventEmitters should only be used for Output properties. The proper way is to use a Subject, BehaviorSubject, or Observable, and we normally directly subscribe to these, hence we don't need a getNavChangeEmitter() method anymore. – Mark Rajcok Jun 17 '16 at 15:14
  • I think the link to the cookbook example needs to be updated: https://angular.io/guide/component-interaction#parent-and-children-communicate-via-a-service – BlackICE Feb 27 '19 at 19:18
7

Using alpha 28, I accomplished programmatically subscribing to event emitters by way of the eventEmitter.toRx().subscribe(..) method. As it is not intuitive, it may perhaps change in a future release.

Razvan Dumitru
  • 11,815
  • 5
  • 34
  • 54
Runeboy
  • 79
  • 2
  • 1
    perhaps provide a simple example for people to use if they stumble upon this post? It wasn't me downvoting btw ;) – Per Hornshøj-Schierbeck Jul 08 '15 at 06:07
  • 2
    Don't know why it was down-voted, it certainly works. Simply put, to subscribe to an event emitter "click", use `click.toRx().subscribe(your_callback_function)` – Runeboy Jul 10 '15 at 11:42
  • I think it might have been downvoted because it's lacking a working example? I know it would be worth an upvote and perhaps an accepted answer if it had either a link to a pluncker/jsfiddle fiddle or even some lines of code to display the syntax better ;) – Per Hornshøj-Schierbeck Jul 10 '15 at 11:44
  • I am getting EXCEPTION: ReferenceError: click is not defined @Runeboy Can you please provide a plnkr for your solution? – Benjamin McFerren Nov 04 '15 at 23:08
  • You need to define an EventEmitter called click for it to work – Mazen Elkashef Apr 16 '20 at 14:49
0

Sometime quick fix of library cause that added event import like

import { EventEmitter } from 'events';

You must change it with core libray using subscribe

import { EventEmitter } from '@angular/core';
ebey
  • 93
  • 8