Search code examples
angularrxjsrxjs5reactivebehaviorsubject

Is there a way to unsubscribe to a BehaviorSubject without assignment?


There's time's where I want to subscribe to an observable only to have an emit trigger additional events. For example, subscribing to query parameters in the route so that another event is triggered from an emit - where the value emitted isn' actually important. Or for example if I have a BehaviorSubject and need to reload certain components based on changes to that subject. In these cases the observable isn't meant to be consumed by a template and the values they emit is for the most part disregarded.

Ideally I would write something like:

ngOnInit() {
    this.service.behaviorSubject.subscribe(
        () => callLocalMethod()
    )
}

The problem is that angular doesn't unsubscribe from that subscription once the component is destroyed, meaning that every time the component is initialized and destroyed a new subscription is created in addition to the old one and emits from the observable are called for each of those redundant subscriptions. Unsubscribing in the OnDestroy lifecycle hook is not possible (from what I can tell) since the subscription isn't assigned to a property - but assigning it to a property seems unnecessary for this use case.

So what I end up having to do is create a property for every single subscription even if that property is never going to be used, assigning it to the subscription, and then calling unsubscribe() on it when the component is destroyed.

This seems really redundant - is there a better approach to this? Handling the subscription with an async pipe isn't an option, and using EventEmitter instead also isn't an option since often times want it to behave like a hot observable.


Solution

  • You can use another subject:

    private destroy$ = new Subject();
    
    constructor() {
      obs$
        // Ensures that we complete when the component is
        // destroyed. Completing will automatically unsubscribe. 
        .takeUntil(this.destroy$)
        .subscribe();
    } 
    
    ngOnDestroy() {
      this.destroy$.next();
      this.destroy$.complete();
    }
    

    The nice thing about this is that it scales nicely as the boilerplate is only needed once regardless of the number of subscriptions you manage.

    Also note that ActivatedRouter actually completes params / paramMap when the component is destroyed, so parameter changes do not need to be unsubscribed from manually.