4

Using Angular, I have a service that is provided in a component. The service has an observable and the component is subscribed to this observable. I had expected I didn't need to unsubscribe from the subscription/observable as the service should be destroyed with the component and thus also the observable. However a quick test shows the observable to be alive.

What is happening out of my sight? Does the observable run outside the service? Or does the service not actually get destroyed when the component in which it was provided gets destroyed?

Superman.Lopez
  • 1,332
  • 2
  • 11
  • 38
  • 2
    The service doesn't stop existing when the component gets destroyed. [Useful resource](https://stackoverflow.com/questions/38008334/angular-rxjs-when-should-i-unsubscribe-from-subscription) – Giovani Vercauteren Dec 12 '19 at 12:46
  • 2
    Yes, Why service should also be destroyed? Services are by default singleton, they are not generated each time they are injected – canbax Dec 12 '19 at 12:49
  • 3
    @canbax Services provided at component level are destroyed. It means they can implement ngOnDestroy hook. But it doesn't mean that it will automatically clean all subscriptions especially if we create Observable with setInterval inside and don't return clear method with clearInterval – yurzui Dec 12 '19 at 13:00
  • @yurzui You are right. By default, they are provided in root level. I don't know much about providing them in component level. – canbax Dec 12 '19 at 13:07
  • 1
    @canbax I noticed in the question `In my component` ... `providers: ...` – yurzui Dec 12 '19 at 13:11
  • It's also explicitly mentioned in the first and last sentence of my question. :) – Superman.Lopez Dec 12 '19 at 13:12
  • @yurzui I used the interval just as a quick way to check if the observable is destroyed. :) If it was an observable that is only triggered by components/child components in/to which it was provided, would it be destroyed? Or would it still hold in memory its last value? – Superman.Lopez Dec 12 '19 at 13:21
  • Here is a [stackblitz](https://stackblitz.com/edit/angular-vxvshh) showing that the subscription continues. I don't know all the cases that are covered by garbage collection but I always heard you have to unsubscribe from infinite observables. Maybe the resources supplied here help https://stackoverflow.com/q/38008334 – frido Dec 12 '19 at 14:44

5 Answers5

0

You have a memory leak if you subscribe to an observable that does not complete. Even if the service is provided by the component, you created an observable in the service and returned it to the component. Now the observable is referenced by the component and has nothing to do with the service that created it any more. When the component is destroyed it will be marked for garbage collection but still exists in memory until the garbage collector cleans up the resources. You still need to unsubscribe from observables that do not complete.

There are a few options

  1. Use the async pipe as it will manage subscriptions for you and unsubscribe
  2. Keep a reference to the subscription and call unsubscribe on ngOnDestroy
  3. Use take until with a subject and make the subject emit an ngOnDestroy.

The nice thing about takeUntil is you can use it to manage multiple subscriptions instead of having to track each subscription individually.

1: async pipe

data$ = this.testService.testObservable();

and in the view

<ng-container *ngIf="data$ | async as data">
  {{ data | json }}
</ng-container>

2: unsubscribe

this.subscription = this.testService.testObservable().subscribe(data => console.log(data));

ngOnDestroy() {
  if (this.subscription) {
    this.subscription.unsubscribe();
  }
}

3: takeUntil

finalise = new Subject<void>();

this.subscription = this.testService.testObservable().pipe(takeUntil(this.finalise)).subscribe(data => console.log(data));

ngOnDestroy() {
  this.finalise.next();
  this.finalise.complete();
}
Adrian Brand
  • 20,384
  • 4
  • 39
  • 60
0

Use takeUntil for your scenario. You need to create/initialize a subject which will be completed on component destroy.

private unsubscribe: Subject<void> = new Subject<void>();


this.testService.testObservable().pipe(takeUntil(this.unsubscribe))
.subscribe(data => console.log(data));

Complete subscription on component destruction

Using a subject and takeUntil, you can unsubscribe to multiple observables in one go at ngOnDestory.

public ngOnDestroy(): void {
    this.unsubscribe.next();
    this.unsubscribe.complete();
  }
Ankit Kapoor
  • 1,615
  • 12
  • 18
0

When we are returning or using intervals then observable will continuously receive stream until we stop it. For best practice you should use interval observable and RxJS operator to automatic destroy the subscription.

// RxJS v6+
import { interval, timer } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

//emit value every 1s
const source = interval(1000);
//after 5 seconds, emit value
const timer$ = timer(5000);
//when timer emits after 5s, complete source
const example = source.pipe(takeUntil(timer$));
//output: 0,1,2,3
const subscribe = example.subscribe(val => console.log(val));

[Note] if you are using setInterval then you need to explicitly unsubscribe the subscription using ngOnDestroy

  • The timer in my question was just to check if the observable was still alive. – Superman.Lopez Dec 13 '19 at 08:08
  • then this scenario will come only if your service is singleton. Are you using providerIn: root. – Ramneek Sharma Dec 13 '19 at 08:26
  • Are you saying my observable was not destroyed, only because I had implement setInterval as my test case? Or other observables would have been kept alive as well on destroying the service? – Superman.Lopez Dec 13 '19 at 08:29
  • No, I am talking about use case. Is your service provided at root and behaving as singleton. In that case, your observable subscription will persist – Ramneek Sharma Dec 13 '19 at 11:53
-1

First point to note is that an Angular Service is singleton and does NOT get destroyed.

Secondly your observable is subscribed in your Component and not in your Service. and finally to answer your question,

The subscriptions are not destroyed when the the component is destroyed. You will have to manually unsubscribe. There are several ways to do this, easiest being

- Dont subscribe, instead use Async pipe in your template.

Read this blog post in medium to know more on better ways to unsubscribe all your subscriptions on the component

Tarun1704
  • 181
  • 1
  • 4
  • 5
    I DOES get destroyed if it was provided on component level. – yurzui Dec 12 '19 at 12:57
  • 2
    I don't like this comment `Dont subscribe, instead use Async pipe in your template.`. There's many situations where the async pipe can not be used. – Giovani Vercauteren Dec 12 '19 at 12:59
  • Yes, there are situations where we cannot use async pipe, for those subscriptions, we have to make sure we unsubscribe on ngDestroy. – Tarun1704 Dec 12 '19 at 13:12
  • They are singleton if they are **provided in root level.** – canbax Dec 12 '19 at 13:12
  • "First point to note is that an Angular Service is singleton and does NOT get destroyed." Stop commenting stupid things if you don't know what you're talking about. You can provide a service in the component and that's destroyed when the component is destroyed, that service isn't a singleton. Go back to the documentation and then comment on stackoverflow, some beginners actually take these answers for granted – LarryP Mar 09 '23 at 11:35
-1

Following the comments of yurzui I conclude the following:

Despite the service being destroyed (as it was provided in the component that got destroyed) the observable and subscription continue to work outside the service and component. I assume this doesn't get collected at some point, so I explicitly clean it up.

My question was not how to unsubscribe from the observable, but regardless I thought it would be useful to share my actual solution to managing the subscriptions I had expected to be destroyed. I created a destroy() function on the service which the parent component calls in the ngOnDestroy cycle. In this function I emit complete from all infinite observables in the service. This saves me from repeating myself and unsubscribing in all the child components.

In my service:

private subject = new BehaviorSubject<string>(null);

public testObservable(): Observable<string> {
    // ... 
    this.subject.next('result');
    return this.subject.asObservable();
}

destroy() {
    this.subject.complete();
}

In my component:

ngOnInit() {
   this.testService.testObservable().subscribe(data => console.log(data));
}

ngOnDestroy() {
    this.testService.destroy();
}

Edit

I have included a working stackblitz in case there is some uncertainty about what my solution is: https://stackblitz.com/edit/destroyservice. What I like about it is that I unsubscribe from 6 subscriptions with 3 lines of code, and I don't need to include ngOnDestroy in any of the child components.

halfer
  • 19,824
  • 17
  • 99
  • 186
Superman.Lopez
  • 1,332
  • 2
  • 11
  • 38
  • This is not a solution for a service, a service is reusable and this makes it useable only once. Once destroyed is called the service will not work the next injection request. – Adrian Brand Dec 13 '19 at 04:42
  • This is not true, as stated in my question and answer the service is provided in the component and it will be reusable next time the component is initialised. – Superman.Lopez Dec 13 '19 at 05:25
  • A service should be designed to be reusable, assuming it is going to be provided in the component is a bad design. You may as well just have the logic in the component as services are designed for shared objects. – Adrian Brand Dec 13 '19 at 06:34
  • Why would providing a service in the component be a bad design? And if so bad, why is Angular designed to provide services on a component level? Also the service is reusable, its instance is used in the parent components and its nested components. – Superman.Lopez Dec 13 '19 at 08:05
  • Providing a service in the component is fine, it is the design of the service is what I am saying is a bad design. – Adrian Brand Dec 13 '19 at 08:16
  • My service is specific to the component and child components in which it is used as the observables are specific to this component, it's provided as a single instance to the main component and its child components. It allows me to share objects by injecting the service into the components that need the objects (and not inject into the components that don't need it). To me that is a good example of how to use a service. Can you elaborate why the design of the service is bad? – Superman.Lopez Dec 13 '19 at 08:21