Sunday, 30 April 2017

Angular automatically unsubscribe from Observable


RxJs is amazing library but it come with the problem and problem is two types, Hot and Cold observable, from one your need to unsubscribe from other not.

Even people understand the difference sometimes they just simply forget to unsubscribe, and same happened in my project there were couple of bugs simply because developer forgot to unsubscribe.

One of the simplest solution is to hold subscription properties and on ngDestroy just simply call unsubscribe.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnDestroy, OnInit {
  private intervalSubscription: Subscription;
  ngOnInit(): void {
    this.intervalSubscription = Observable.interval(100)
      .subscribe((i) => console.log("Call without unsubscribe"))
  }

  ngOnDestroy(): void {
    this.intervalSubscription.unsubscribe();
  }

  title = 'app works!';
}

But first I need to remember about that, second I need to hold extra property which in many cases i just dont need.

So I came up with simple solution which works for me

First I've introduced base-component.ts with two methods one to add subscription and second will call unsubscribe on all items

import { Subscription } from 'rxjs/Subscription';
import { Observable } from 'rxjs/Observable';
import { OnDestroy } from '@angular/core';
import "./safe-subscribe"

export class BaseComponent implements OnDestroy {
    private _subscriptions: Subscription[] = [];
    public ngOnDestroy(): void {
        for (let sub of this._subscriptions) {
            sub.unsubscribe();
        }
    }

    public markForSafeDelete(sub: Subscription) {
        this._subscriptions.push(sub);
    }
}
Second I have extended observable and added new method safeSubscribe which is just a wrapper and require one more parameter of component instance to be base component

import { Observable } from "rxjs/Observable";
import { BaseComponent } from "app/base-component";
import { Subscription } from "rxjs/Subscription";

export function safeSubscribe(this: Observable, component: BaseComponent,
    next?: (value: T) => void, error?: (error: T) => void, complete?: () => void, ): Subscription {
    let sub = this.subscribe(next, error, complete);
    component.markForSafeDelete(sub);
    return sub;
}
Observable.prototype.safeSubscribe = safeSubscribe;

declare module 'rxjs/Observable' {
    interface Observable {
        safeSubscribe: typeof safeSubscribe;
    }
}

Now final solution will look like

export class AutoUnsubscribeComponent extends BaseComponent implements OnInit {
  ngOnInit(): void {
    Observable.interval(100)
      .safeSubscribe(this, (i) => console.log("Call without unsubscribe"))
  }
}

If component is in memory I can see console log is printing message if I remove component then nothing is added to console anymore

In addition safeSubscribe still returns Subscribtion so event if you need you can use it

The only problem now each developer should know what to call but i will think how to prevent using subscribe in my code using linting.

Source code can be found here
Volodymyr Bilyachat Web Developer