Should I unsubscribe Observables lists?

As you know, for observables lists we need to use subscribe to use the data, but I have small question :

SHOULD I unsubscribe the subscription after unload the page ? Or there is no problems if I keep use the application without unsubscribe the list !

Yes you must unsubscribe. Memory leak otherwise. Or use the async pipe, which unsubscribes automatically.

2 Likes

To expand on @AaronSterling’s comment, I never explicitly type unsubscribe any more, after reading this article. Instead, I use the following idiom:

private _tripwire: Subject<boolean>;

ionViewWillEnter(): void {
  this._tripwire = new Subject<boolean>();
  observableFoo()
    .pipe(takeUntil(this._tripwire))
    .subscribe(foo => this.foo = foo);
  observableBar()
    .pipe(takeUntil(this._tripwire))
    .subscribe(bar => this.bar = bar);
  // repeat ad infinitum  
}

ionViewWillLeave(): void {
  // it would be nice to be able to just complete, but that won't trigger takeUntil  
  this._tripwire.next(true);
  // strictly speaking probably not necessary, but i do it in case the previous line gets to go away at some point
  this._tripwire.complete();
  this._tripwire = undefined;
}

Instead of willEnter/willLeave, these can be paired up in didLoad/willUnload or any other set of matching lifecycle events as befits your situation.

8 Likes

I use a very similar approach, but don’t include

this._destroy$ = undefined;

Where _destroy$ = _tripwire.
What’s your reasoning for that additional line? Should I be using it?

Edit: I’m using that approach, along with switchMap, combineLatest, etc. in pure Angular projects, not Ionic. I know there’s a difference in how components are “destroyed”

Removing the reference allows javascript to do garbage collection on the new statement?

Maybe so? I was under the impression calling

this._destroy$.complete()

released the subject from having any further effect until the page reinits. I’ll check out that link. Thanks

Complete() ends the stream but does not necessarily clear the reference to the memory used by Subject as referenced by this._destroy

(At least, that would need confirmation)

I see your point. I’ll run some tests today. Curiosity = piqued

Definitely a most practical solution. I use the observable pipe, takeUntil, switchMap, etc. then subscribe because a majority of my data needs to be sorted in some fashion.

But the async pipe is a gift from the computer gods when applicable.

I sure would like to see the ability to sort an observable stream one of these days.

So to summarise: streams that do not complete during the lifetime of component need unsubscription, which ideally should be done by forcing the completion using the patterns shown above (or use async pipe). .

(So I know I can keep my code as is)

1 Like

@Tommertom, So that include assigning undefined to the subject?

1 Like

So to summarise more complete: streams that do not complete during the lifetime of component need unsubscription, which ideally should be done by forcing the completion using the patterns shown above (or use async pipe). And if you reference to your subscription using a variable, assign it to undefined after completion.

(thx @jaydz)

1 Like

Got it. Thanks to you too @Tommertom

He wasn’t undefining the subscription, but the tripwire Subject. To my eye, that’s unnecessary (esp in this toy example) but good style, because if the Subject emits unexpectedly, it could have consequences for everything still listening to it. If you undefine it as part of your teardown logic,you guarantee yourself that a source of hard-to-debug errors will not exist.

All that said, I’d already seen the article @rapropos linked, and I wasn’t convinced by it. I think there’s a benefit to forcing yourself to know exactly what you’re tearing down in your teardown logic. But it’s a very clever idea. Maybe if you have 50 Observables on your page, and you need to edit some in and out in each update? But I only have a few Observables per “code piece.”: (Though I have a couple dozen providers in one project, so I just segmented things that way.)

@AaronSterling, It seems strange that a Subject could emit after completion. Chalk it up to the imperfections of computer tech??

I certainly don’t have 50 observables on any page, but I do tend to have 2 - 3 observables on init that get items like a user id, an app state boolean or string, etc.

Those I always use take(1) on so don’t need to think twice about them. But with the addition of a couple more Observables, I guess I just prefer reducing my code by a few lines when possible. Or I just like feeling clever.

Though avoiding importing { Subscription }, Typing each subscription, then assigning and unsubscribing does seem to reduce my lines to a decent degree.

I am curious about

that line though.

I like having teardown logic live right next to buildup logic. It’s sort of a cousin to why I prefer using initializers over splitting up declarations and doing initialization in constructors (where possible). Then if I add a new Observable later, I only have to edit one place, because it can share the same tripwire. Golang has this concept burned into the language itself with its defer statement, and I find it very helpful in dealing with things like rolling back SQL transactions in error situations.

1 Like

I second that notion.

Looks like I’m on team @rapropos on this one.

Do you mean:

thing = {
  buildUp: doThis(),
  tearDown: doThat(),
  whileAlive: doMainTask()
}

because I can see the benefit of that. I now wonder if I could extend Observable to that, using ideas from this thread? That might be super clean.

If you screwed up your code and the Subject did something you didn’t intend for it to do. Finding that error can be a pain and a half, because your mistake might be removed from the effect you are seeing.

Sorry to interject, but this is how I am implementing the “tripwire effect”

  this.detailService.filterSolutions().pipe(takeUntil(this.destroy$))
  .subscribe(data => { 
   if (data) { this.solutions = data; }
   });

Not sure if it’s applicable to the go-between with you and @rapropos
I’m fetching a switchMap / query action from detailService, and implementing it in my constructor.
My provider of course contains some logic, i. e

searchSolution(item: Problem | null) {
   this._solutionTerms$.next(item);
 }

filterSolutions() : Observable<Solution[]>{
   return this._solutionTerms$.pipe(switchMap(item => getSolutions(item)));
 }