Angular 4+ ngOnDestroy() in service - destroy observable
Solution 1
OnDestroy lifecycle hook is available in providers. According to the docs:
Lifecycle hook that is called when a directive, pipe or service is destroyed.
Here's an example:
@Injectable()
class Service implements OnDestroy {
ngOnDestroy() {
console.log('Service destroy')
}
}
@Component({
selector: 'foo',
template: `foo`,
providers: [Service]
})
export class Foo implements OnDestroy {
constructor(service: Service) {}
ngOnDestroy() {
console.log('foo destroy')
}
}
@Component({
selector: 'my-app',
template: `<foo *ngIf="isFoo"></foo>`,
})
export class App {
isFoo = true;
constructor() {
setTimeout(() => {
this.isFoo = false;
}, 1000)
}
}
Notice that in the code above Service
is an instance that belongs to Foo
component, so it can be destroyed when Foo
is destroyed.
For providers that belong to root injector this will happen on application destroy, this is helpful to avoid memory leaks with multiple bootstraps, i.e. in tests.
When a provider from parent injector is subscribed in child component, it won't be destroyed on component destroy, this is component's responsibility to unsubscribe in component ngOnDestroy
(as another answer explains).
Solution 2
Create a variable in your service
subscriptions: Subscriptions[]=[];
Push each of your subscribe to the array as
this.subscriptions.push(...)
Write a dispose()
method
dispose(){
this.subscriptions.forEach(subscription =>subscription.unsubscribe())
Call this method from your component during ngOnDestroy
ngOnDestroy(){
this.service.dispose();
}
Solution 3
I prefer this takeUntil(onDestroy$)
pattern enabled by pipable operators. I like that this pattern is more concise, more clean, and it clearly conveys the intent to kill a subscription upon execution of the OnDestroy
lifecycle hook.
This pattern works for services as well as components subscribing to injected observables. The skeleton code below should give you enough detail to integrate the pattern into your own service. Imagine you're importing a service called InjectedService
...
import { InjectedService } from 'where/it/lives';
import { Injectable, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class MyService implements OnDestroy {
private onDestroy$ = new Subject<boolean>();
constructor(
private injectedService: InjectedService
) {
// Subscribe to service, and automatically unsubscribe upon `ngOnDestroy`
this.injectedService.observableThing().pipe(
takeUntil(this.onDestroy$)
).subscribe(latestTask => {
if (latestTask) {
this.initializeDraftAllocations();
}
});
}
ngOnDestroy() {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
The topic of when/how to unsubscribe is covered extensively here: Angular/RxJs When should I unsubscribe from `Subscription`
Solution 4
Just to clarify - you don't need to destroy Observables
but only the subscriptions made to them.
It seems like others have pointed out that you are now able to use ngOnDestroy
with services as well. Link: https://angular.io/api/core/OnDestroy
Solution 5
Caution if using tokens
In trying to make my application as modular as possible I'll often use provider tokens to provide a service to a component. It seems that these do NOT get their ngOnDestroy
methods called :-(
eg.
export const PAYMENTPANEL_SERVICE = new InjectionToken<PaymentPanelService>('PAYMENTPANEL_SERVICE');
With a provider section in a component:
{
provide: PAYMENTPANEL_SERVICE,
useExisting: ShopPaymentPanelService
}
My ShopPaymentPanelService
does NOT have its ngOnDestroy
method called when the component is disposed. I just found this out the hard way!
A workaround is to provide the service in conjunction with useExisting
.
[
ShopPaymentPanelService,
{
provide: PAYMENTPANEL_SERVICE,
useExisting: ShopPaymentPanelService
}
]
When I did this the ngOnDispose
was called as expected.
Not sure if this is a bug or not but very unexpected.
mperle
Updated on February 11, 2022Comments
-
mperle over 1 year
In an angular application we have
ngOnDestroy()
lifecycle hook for a component / directive and we use this hook to unsubscribe the observables.I want to clear / destory observable that are created in an
@injectable()
service. I saw some posts saying thatngOnDestroy()
can be used in a service as well.But, is it a good practice and only way to do so and When will it get called ? someone please clarify.
-
mperle about 6 yearsThank you for your reply. Do we have any idea when this ngOnDestroy will be called. ?
-
mperle about 6 yearsyes it says its a cleanup call before the directive or component gets destroyed. but i just want to understand whether is it applicable for service as well ?
-
Aravind about 6 yearsNo services will be cleared when the module is unloaded
-
Aravind about 6 yearslife cycle hooks are not applicable for
@injectables
-
Estus Flask about 6 years@Aravind I'm not sure when they were introduced but they are.
-
Aravind about 6 years@estus can you elaborate. the comment
-
Estus Flask about 6 yearsLifecycle hooks are applicable to providers as well. Since v4, I guess.
-
Aravind about 6 years@estus I tried life cycle hooks at service it is not working in the 4.x versions too
-
Estus Flask about 6 yearsI've posted an answer that explains this.
-
Shumail about 6 yearsNo
class Service implements OnDestroy
? And what do you think when this is called if service is provided on module level -
Estus Flask about 6 years
implements OnDestroy
doesn't affect anything but can be added for completeness. It will be called when a module is destroyed, likeappModule.destroy()
. This may be useful for multiple app initializations. -
aruno about 5 years@Aravind You can also just create one
Subscription()
object ad hoc and add to it. So you end up with_subscriptions = new Subscription();
thenthis._subscriptions.add(.....)
and inngOnDestroy()
you just unsubscribe and don't need the array logicthis._subscription.unsubscribe()
. -
Aravind about 5 yearsCan you please elaborate more on it
-
Aravind about 5 yearsIt's something new to me I'll check this out and see. Thanks mate
-
Ali Abbaszade almost 5 yearsis unsubscribe necessary for every component that uses services?
-
Estus Flask almost 5 years@AliAbbaszade May depend on what it unsubscribes from. But likely yes, otherwise this will result in memory leaks.
-
Andrei Sinitson about 4 yearsGreat hint! I was wondering why it wasn't working in my case (I was using abstract class-interface as a token for concrete implementation).
-
user2900572 about 4 yearsA lifecycle hook that is called when a directive, pipe, or service is destroyed. Use for any custom cleanup that needs to occur when the instance is destroyed.
-
compuguru almost 4 yearsThe Plunker wasn't working for me, so here's a StackBlitz version of the example: stackblitz.com/edit/angular-mggk9b
-
Tim almost 4 yearsI think you are missing implements OnDestroy in the declaration of your component (Foo)
-
Jasmin over 3 yearsI had some trouble to understand this. But this discussion helped me understand the difference between local and global services: stackoverflow.com/questions/50056446/… Whether you have to "clean up" or not depends from the scope of your service, I think.
-
Akxe over 2 yearsIf some want to vote to fix the module not being destructed, you can do so here github.com/angular/angular/issues/37095#issuecomment-854792361
-
Eneko over 1 yearI think $ prefix is usually used for observables but no for subscriptions. The convention I follow: rat is a Rat object, rats is Rat[] (or List<Rat>) and rat$ is Observable<Rat>. Anyway, IMHO this is best answer.