18

In my ngOnInit method I am subscribing to the Router like this:

this.router.events.subscribe(
  event => {
    if (event instanceof NavigationEnd) this.clearMessages();
  }
);

Normally, for Observables outside of the HttpClient package I call unsubscribe in the ngOnDestroy method but when I tried that here I discovered that this.router.events does not have such a method. Am I mistaken or is there something different about this Observable? Why would unsubscribe not be implemented?

AlanObject
  • 9,613
  • 19
  • 86
  • 142
  • 1
    If you console.log `router.events` you will see it is a Subject, so it has an unsubscribe method when you store the subscription. `let sub = this.router.events.subscribe()` if you console.log(sub) you will see it has the unsubscribe method. – LLai Jan 19 '18 at 01:53
  • @LLai the declaration for events is: **readonly events: Observable;** on line 159 in router.d.ts. Are you sure that is right? – AlanObject Jan 19 '18 at 02:04
  • Yes he is right, because the result of `subscribe()` is what gets stored in `sub`. – Richard Matsen Jan 19 '18 at 03:21
  • It's fairly easy to see if you need to unsubscribe. Put a console.log in the above code and see if it keeps emitting after you navigate to another page. – Richard Matsen Jan 19 '18 at 03:26
  • @AlanObject yep. Eventhough it is readonly you can still subscribe to it. You just can't assign anything to it. – LLai Jan 19 '18 at 03:27

3 Answers3

29

I discovered that this.router.events does not have such a method

You call unsubscribe on subscriptions, not observables. And this.router.events is an observable, not subscription. So the following would work:

const subscription = this.router.events.subscribe(...);   
subscription.unsubscribe();
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • 2
    Do this in ngOnDestroy() {..} – Gopherkhan Mar 10 '20 at 18:10
  • @MaxKoretskyi if we are subscribing router.evets in app.component.ts, then how can we unsubscribe() from app.component.ts? Because app.component.ts is main root component which never going to call ngOnDestroy() method throughout of application life-cycle. Is there any way to unsubscribe() router.evets from app.component.ts? – Developer Jul 12 '20 at 08:00
6

You are right, some observables need no manual unsubscription from, observables like:

  1. Router, HTTP, HTTPClient, because these are single emission observables, although implicitly, and
  2. Explicit single emission observables (e.g., finite), like first()

Similar information can be found on this reddit site also. This also means that no need to worry about memory leaks from observables in these classes.

mohsenmadi
  • 2,277
  • 1
  • 23
  • 34
  • 12
    I wouldn't say the observables from Router (Router + ActivatedRoute) are single emission observables. They can emit multiple times depending on if the events, params, queryParams, etc change. From this [stackblitz](https://stackblitz.com/edit/llai-router-cleanup?file=app%2Fcomponent-2%2Fcomponent-2.component.ts) it looks like ActivatedRoute observables clean up properly when the component is destroyed. However you can see `router.events` does not cleanup. I assume this is intentional since there are 3 navigation events after the component is destroyed. – LLai Jan 19 '18 at 04:24
  • Thanks for the enlightenment and link. So, although `router.events` in particular don't clean up, is it safe to assume they won't cause memory leaks since they won't emit events when their parent component is destroyed? This of course can't be said about custom, infinitely emitting observables for example. – mohsenmadi Jan 19 '18 at 04:53
  • 1
    I believe you would run into the same issue. It will still emit events even if the parent component is destroyed. (Since there are navigation events after component destroy). I'm unsure of what are the use cases for router.events. If you use it say at the top level component of your app. It only gets subscribed to once so no need to clean it up since you will only have 1 subscribe running at a time. If you do however subscribe to it in multiple components then it looks like you will have to clean it up properly. – LLai Jan 19 '18 at 15:56
2

This is work for me. Full example.

import { Subject } from 'rxjs/internal/Subject';
import { Subscription } from 'rxjs';

export class ExampleComponent implements OnInit, OnDestroy {
  routerEventSubscription: Subscription;
  private destroy$: Subject<boolean> = new Subject<boolean>();

  ngOnInit() {
    this.routerEventSubscription = this.router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        // your code
      }
    })
  }

  ngOnDestroy(): void {
    this.routerEventSubscription.unsubscribe();
    this.destroy$.next(true);
  }

}
James L.
  • 12,893
  • 4
  • 49
  • 60
deeplogger
  • 71
  • 1
  • 3
  • you also need to call `this.destroy$.complete()` at the end of `ngOnDestroy`. See also https://stackoverflow.com/a/52232393/1438576 – runderworld Feb 10 '23 at 00:29