57

Below is a simple example of what I am asking about:

class AppComponent {

  someInput: FormControl = new FormControl('');
  private subscription: Subscription;

  constructor(){

    this.subscription = this.someInput.valueChanges
        .subscribe(() => console.log("testing"));
  }
}

I have 2 questions:

1) Do I have to eventually unsubscribe from this? 2) Most importantly, whatever the answer is to #1, WHY is that the answer?

Any insight would be appreciated!

Thanks!

user1902183
  • 3,203
  • 9
  • 31
  • 48

4 Answers4

70

UPDATE OCT 2020

This question gets a lot of SEO traffic and whilst my answer is still technically correct, I strongly advise you to read this.


  1. Yes, you do. Note that you do not need to unsubscribe from http calls because they auto-complete themselves.

  2. You must unsubscribe to prevent memory leaks and to avoid unexpected side-effects in your application.

For example, when a component is destroyed, the subscription will remain if you don't unsubscribe from it. The next time the user navigates back to that page and the component gets loaded again, it will create another subscription to valueChanges - now you have 2 active subscriptions processing data simultaneously and that will lead to unexpected behavior in your app.

There is a relatively easy way to setup auto-unsubscribing.

The RxJS way is to use takeUntil - it basically lets you set up a subscriber which keeps listening UNTIL you tell it not to :)

First we need a destroy subject in our component:

  private destroy$ = new Subject();

Then tell it to emit a value when our component is destroyed:

  ngOnDestroy(){
    this.destroy$.next();
    this.destroy$.complete(); 
  }

Now setup your subscribes to use it:

this.someInput.valueChanges
  .debounceTime(1000)
  .distinctUntilChanged()
  .takeUntil(this.destroy$)
  .subscribe (newValue => {

All the subscribes setup like this will then auto-unsubscribe when they receive the destroy notification.

This is especially useful if, for example, you have an ngFor that is dynamically adding controls, and each control added has an valueChanges subscription (eg. an array of controls). In such a scenario you don't need to loop through the array in ngOnDestroy and unsubscribe one-by-one. Just use takeUntil :)

I recommend reading Ben Lesh's article (RxJS lead engineer at Google) for a great overview of takeUntil, including pros/cons.

rmcsharry
  • 5,363
  • 6
  • 65
  • 108
  • 13
    Just wondering, when the component is destroyed and is eligible for gc there's nothing else referencing the `someInput` object and in turn its' `valueChanges` observable instance and the subscription function, so they should be eligible for gc as well. So why would you have a memory leak? Does rxjs keep references to the subscription functions in some state _outside_ the `Observable` instance? That sounds unlikely to me but I'm not familiar with the inner workings of rxjs. I see the problem when using `interval` or similar but not in this case. – nej_simon Oct 25 '19 at 09:10
  • 1
    @nej_simon That is a very good question indeed. My understanding is that rxjs observables and subscriptions "live outside of the Angular lifecycle". That is the issue. A lot more info in this question: https://stackoverflow.com/questions/38008334/angular-rxjs-when-should-i-unsubscribe-from-subscription – rmcsharry Oct 26 '19 at 07:57
  • 1
    If i make a console.log inside the subscription method for the valueChanges it does not produce another console log every time my component is destroyed which i guess it would if it keeps hanging in memory? – XRaycat Jan 03 '21 at 23:15
  • 1
    @XRaycat I think you don't see the console log after component is destroyed because the value is not changing, so the event does not fire. – rmcsharry Jan 04 '21 at 15:00
  • @rmcsharry So, if the value is not changing (or we can assume it **wouldn't** change), how it can produce memory leaks? – nmfzone Sep 19 '21 at 06:15
  • @nmfzone If you don't unsubscribe, then the subscription is still active, so the next time that component loads, you will now have 2 subscriptions. Do that enough and you will likely start to see issues. I started to see issues on the 3rd visit to the same component. I don't recall exactly what they were as it was 4 years ago. – rmcsharry Sep 19 '21 at 16:54
  • 1
    Is `.debounceTime(1000)` and `.distinctUntilChanged()` part of the answer? The linked example doesn't add those, but it adds other piped function calls that the OP didn't have, so I'm confused on what is actually required. – xr280xr Oct 28 '21 at 00:35
  • 1
    @xr280xr No they are not, they are to add a delay and to only receive distinct values. But since this is a value change handler on an input, it is usual to use both of these. Read the docs to understand why. – rmcsharry Nov 01 '21 at 05:49
  • isn't `this.destroy$.complete();` unnecessary? – sirbradwick Feb 13 '23 at 19:32
  • @sirbradwick no idea, too long ago, don't work with Angular much now. But I'd advise asking that famous AI Chat Bot everyone keeps chatting about ;) I bet it knows. – rmcsharry Feb 14 '23 at 12:34
5

I highly recommend the rxjs extension https://github.com/ngneat/until-destroy . It will help you write less code.

@UntilDestroy()
@Component({})
export class InboxComponent {
  ngOnInit() {
    this.someInput.valueChanges
      .pipe(untilDestroyed(this))
      .subscribe();
  }
}

jenson-button-event
  • 18,101
  • 11
  • 89
  • 155
2
  1. Yes.
  2. For thing whatever subscribe, you need to unsubscribe to prevent memory leak. There are a few way to unsubscribe. a. "auto unsubscribe after N time" - add .take(numberOfTimes) will unsubscribe after the N time you specified. For example:

    this.subscription = this.someInput.valueChanges
        .take(2) // unsubscribe after 2 times
        .subscribe(() => console.log("testing"));
    

    b. Unsubscribe during component lifecycle manually, normally during ngOnDestroy()

Jecfish
  • 4,026
  • 1
  • 18
  • 16
  • 1
    IMO best way is with [takeUntil](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-takeUntil) – Sergey Sokolov Dec 28 '16 at 15:10
  • depends on situation. If knowing the number of time, then take(n), if knowing that action end until certain scenario or action, then takeUnitl() – Jecfish Dec 28 '16 at 15:15
  • 1
    If you use take(n) or takeUntil(), what happens if the user leaves the view before the Observable had a chance to respond and unsubscribe itself automatically. Wouldn't it be safer to use unsubscribe in the destroy? – Thibs Mar 22 '17 at 13:32
  • 1
    @Thibs, It's probably a bit late, but to answer your question, it's actually the same. When the user leaves the view, the Angular life cycle will call ngOnDestroy and therefore, the this.destroy$.next(true) would be called, thus unsubscribing from the observable. – n4nd0_o Mar 28 '18 at 08:42
  • My way of doing this is add subscriptions to array. So: this.subscriptions.push( this.formfield.valueChanges.subscribe( value => console.log('value: ', value)) ... then in the onDestroy function you only need: this.subscriptions.map( sub => sub.unSubscribe()) – Spock Feb 15 '21 at 13:53
0

Yes we need to unsubscribe valueChanges subscription to prevent memory leaks. You can check simply check demo here

pathe.kiran
  • 2,444
  • 1
  • 21
  • 27