Reputation: 1
When we use ngrx with angular, let's say I fetch data from a selector in a component in a variable, in ngOnDestroy
should I unsubscribe to using the unsubscribe method or does ngrx
select method do it for me?
export class FooComponent {
data$: Observable<any>;
ngOnInit(){
data$ = this.store.select(selectData);
}
ngOnDestroy(){}
}
Upvotes: 0
Views: 153
Reputation: 409
NO. Select will not automatically unsubscribe you will have to do it manually. Either hold subscription in a variable and unsubscribe to it in ngOnDestroy() hook or you can use operators like takeUntil or takeUntilDestroyed to automatically unsubsidized.
Upvotes: 0
Reputation: 1598
There are many ways to unsubscribe from an observable! As far as I know, ngrx
can't do it for you given it's something up to the developer to decide.
And about the topic of subscribing to observables to display some data in the template:
AsyncPipe
The recommended way to subscribe to observables is to use the AsyncPipe
in the template if you can. This way, Angular will take care of unsubscribing when the component is destroyed. Try to use it as the default and as much as you can.
Unless you use signals, in that case signals are automagically handled without the async pipe 🪄
Angular managing observable subscriptions has more benefits than that though. It will help if transitioning to a zone-less app or if you move to an
OnPush
change detection strategy. As Angular will mark the view as dirty when a new value comes through the observable, hence will help with change detection management.
For instance if you want to dump your data (will assume it's JSON and want to dump it all) here's how the template would look like
<pre>{{ data$ | async | json }}</pre>
If you can't use the AsyncPipe
, then you can manually subscribe and unsubscribe when the component gets destroyed in the ngOnDestroy
lifecycle hook as you intended.
export class FooComponent implements OnDestroy {
data: any
dataSubscription: Subscription;
ngOnInit(){
this.dataSubscription = this.store.select(selectData)
.subscribe((data) => this.data = data)
}
ngOnDestroy(){
this.dataSubscription.unsubscribe()
}
}
Then in your template:
<pre>{{ data | json }}</pre>
takeUntil
operatorIf you start having more than one subscription, it can be messy to have all of those subscriptions around. You can store them in a subscriptions
array and then unsubscribe
from all of them in the ngOnDestroy
. Or you can also use takeUntil
and one extra observable to unsubscribe them all:
export class FooComponent {
unsubscribeSignal: Subject<void> = new Subject();
data: any
moarData: any
ngOnInit() {
this.data = this.store.select(selectData)
.pipe(takeUntil(this.unsubscribeSignal))
.subscribe((data) => this.data = data);
this.moarData$ = this.store.select(selectMoarData)
.pipe(takeUntil(this.unsubscribeSignal))
.subscribe((moarData) => this.moarData = moarData);
}
ngOnDestroy(){
this.unsubscribeSignal.next();
this.unsubscribeSignal.unsubscribe();
}
}
You can then use
data
andmoarData
in your template as usual (like in previous example)
There are some libraries out there that help you with this strategy by creating the extra observable for you. But haven't used them and they seem to be outdated, so can't recommend any
Beware that the following APIs are in developer preview, so they are not stable yet and may change in the future
If you want to start using signals around, you can also convert your observable to a signal with toSignal
API that comes built-in in the @angular/core
package since Angular v16. This way the signal will get destroyed for you when the component is destroyed.
Here's how:
export class FooComponent {
data: Signal<any>;
ngOnInit(){
data = toSignal(this.store.select(selectData))
}
}
Then, in your template (again using JSON pipe to dump the whole contents):
<pre>{{ data() | json }}</pre>
takeUntilDestroyed
APIThere's also a new takeUntilDestroyed
API since Angular v16 that can help you unsubscribe from one or many observables automatically when the component gets destroyed. Similar to the way described above to unsubscribe from more than one observable. But with the benefit of don't having to maintain yourself the extra observable to indicate to the other observables that the component is being destroyed.
However, the API can only be used in an injection context (for instance calling it when defining the observables as class fields or in the constructor).
export class FooComponent {
data: any
// 👇 Important to be in an injection context
// Otherwise, won't work
constructor() {
this.store.select(selectData).pipe(takeUntilDestroyed())
.subscribe((data) => this.data = data)
}
}
You can also use it outside of an injection context if you pass it a DestroyRef
. Checkout this blog post explaining about this new API a bit more.
Upvotes: 1