Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What approach is better for killing an observable, provided for takeUntil operator, and why?

I have a question about one of the common pattern for the unsubscribing with the takeUntil operator for Angular and RxJs. In this article, it 's under the third position. For example, we have such code in a component class:

  private destroy$: Subject<boolean> = new Subject();

  ngOnInit() {
     this.control.
     .pipe(takeUntil(this.destroy$)
     .subscribe(doSmthngFunc); 
  }

  ngOnDestroy() {
    this.destroy$.next(true);
    // Which next line of code is correct?
    // this.destroy$.complete()     // this one?
    // this.destroy$.unsubscribe()  // or this one?
  }

The first line this.destroy$.next(true) is totally clear. But the second is not. If we look into the realization of these methods, we find that they have somewhat similar behavior. complete(): unsubscribe():

As I understand semantically complete() is preferable, because we call next() for the first and the last time during the component life and then we finished with this Subject, treated as Observable and can invoke complete(). These methods belong to the observer and unsubscribe belong to the observable, and we have no subscriptions to unsubscribe from. But under the hood, these methods have a similar code:

    this.isStopped = true; // both

    this.observers.length = 0; // complete
    this.observers = null;     // unsubscribe

    this.closed = true;        // only unsubscribe

Theoretically complete() has delayed effect as it's may invoke complete() on every observer subscribed, but we have no observers on destroy$. So the question - which way is more preferable, less error-prone, and why?

like image 416
Oleksandr K Avatar asked Nov 17 '22 05:11

Oleksandr K


1 Answers

Destruction of a component is a singular event.

   this.destroy$.next();
   this.destroy$.complete();

Ensures that the subject emits only once and completes.

For example;

    const destroy$ = new Subject();

    destroy$.subscribe(v => console.log("destroyed"));

    destroy$.next();
    destroy$.complete();
    destroy$.next();

    // the above prints "destroyed" only once.

It's not a technical requirement to complete, but if you don't then business logic that depends upon completion instead of emission will not always work, and might leak memory.

For example, the following would be a memory leak in RxJs.

   destroyed$.subscribe(() => {
       console.log('This might leak memory');
   });

The above could leak memory because the subscription never ends and the observable never completes. You can fix the leak by adding a first() operator or making sure the subject is completed. RxJS does not know that the subject will only emit one value, and so you have to tell it. Otherwise subscribers remain bound to the stack frame and are not garbage collected. So while the garbage collector might collect the component after it is used if anything references the stack frame of the subscriber, then that subscription lives on.

So call complete on your destroy subjects so that other people don't make mistakes.

this.destroy$.unsubscribe()

Calling unsubscribe on a subject might not have an effect on downstream operators that create inner subscriptions. For example, switchMap() and mergeMap() create inner subscriptions.

So you can not manage subscriptions higher up effectively. It's better to unsubscribe from the subscription created when you call the subscribe() method, because this is last in the chain of operators.

like image 151
Reactgular Avatar answered Jan 12 '23 00:01

Reactgular