angularNoob
angularNoob

Reputation: 1

When do you unsubscribe to an observable in angular component?

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

Answers (2)

saurav1405
saurav1405

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

davidlj95
davidlj95

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:

Stable ways

Recommended, automatic subscription: using the 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>

Manual subscription & unsubscription

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>

Manual subscription: with takeUntil operator

If 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 and moarData 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

Unstable, bleeding edge ways (in developer preview)

Beware that the following APIs are in developer preview, so they are not stable yet and may change in the future

With signals

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>

With takeUntilDestroyed API

There'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

Related Questions