26

In my application I have some components that communicate by means of EventService.

@Injectable()
export class EventService {
  public myEvent: EventEmitter<any> = new EventEmitter();
  constructor() {}
}

This service is injected in a EmitterComponent that emits the event when a button is clicked

@Component({
  selector: 'emitter',
  template: `<button (click)="onClick()">Click me</button>`,
})
export class EmitterComponent {
  constructor(private eventService:EventService) {}
  onClick() {
    this.eventService.myEvent.emit();
  }
}

and in a ReceiverComponent that subscribes to the event and for each event received increments a counter

@Component({
  selector: 'receiver',
  template: `Count: {{count}}`,
})
export class ReceiverComponent {
  public count = 0;
  constructor(private eventService:EventService) {
    this.eventService.myEvent.subscribe(() => this.count++;);
  }
}

The application has multiple views (in this example just two): PageA and PageB. EmitterComponent and ReceiverComponent are in PageA. Every time I go to PageB and come back to PageA, a new ReceiverComponent is created and when I click the button in EmitterComponent, the event callback function of ReceiverComponent is executed several times.

To avoid this I unsubscribe ReceiverComponent from myEvent in ngOnDestroy

ngOnDestroy() {
  this.eventService.myEvent.unsubscribe();
}

but this causes the following Exception

EXCEPTION: Error during instantiation of ReceiverComponent!.
ORIGINAL EXCEPTION: Error: Cannot subscribe to a disposed Subject

How can I avoid that? How to unsubscribe correctly?

For a better understanding I've created this plunker where you can see the error and some comments in the console.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
TizianoL
  • 1,271
  • 2
  • 11
  • 13

2 Answers2

36

You get a subscription from .subscribe(). Use its unsubscribe() method to cancel the subscription.

@Component({
  selector: 'receiver',
  template: `Count: {{count}}`,
})
export class ReceiverComponent {
  public count = 0;
  private subscription;
  constructor(private eventService:EventService) {
    this.subscription = this.eventService.myEvent.subscribe(() => this.count++;);
  }
  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

See also

Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • That's exactly what I did, but this causes "Error: Cannot subscribe to a disposed Subject" when I change view to pageB and go back to pageA – TizianoL Jan 21 '16 at 14:44
  • 3
    Doesn't look the same to me. Am I missing something? In your question you use `this.eventService.myEvent.unsubscribe();` vs `subscription.unsubscribe();`. – Günter Zöchbauer Jan 21 '16 at 14:45
  • 1
    Oh, I'm sorry, my bad, I didn't noticed the difference. Now it works, thanks a lot! – TizianoL Jan 21 '16 at 14:51
  • Why do you need this? this.subscription is a class variable and should be automatically destroyed with the rest of the class? – TGH Jan 21 '16 at 19:53
  • 1
    @TGH good question. I don't know if `ReceiverComponent` can be GCed when `eventService.myEvent` still references a closure that references `ReceiverComponent.count`. – Günter Zöchbauer Jan 21 '16 at 19:57
  • @GünterZöchbauer The OP's question that you answer here was posted almost a month after my question that you marked as a dupe of this one was. You have this backwards.... – Michael Oryl Jan 11 '17 at 19:32
  • You are right, somehow looked at a wrong date. – Günter Zöchbauer Jan 11 '17 at 19:44
2

I think your should cancel the subscription, as described below:

export class ReceiverComponent {
  public count = 0;
  private id;

  constructor(private eventService:EventService) {
    this.id = Date.now();
    console.log("ReceiverComponent constructor " + this.id);
    this.subscription = this.eventService.myEvent.subscribe(() => {
      console.log("count " + this.count + " (id ReceiverComponent instance: " + this.id + ")");
      this.count++;
    });
  }

  ngOnDestroy() {
    console.log("onDestroy of ReceiverComponent " + this.id)
    //this cause "Cannot subscribe to a disposed Subject."
    //this.eventService.myEvent.unsubscribe();
    this.subscription.unsubscribe();
  }
}

In fact, EventEmitters are shared observables, i.e. hot observables. Here is a link that could interest you: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/creating.md.

Hope it helps you, Thierry

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360