Home Java 6 ways to unsubscribefrom Observables in Angular

6 ways to unsubscribefrom Observables in Angular

by admin

6 ways to unsubscribefrom Observables in Angular

Reverse side of subscription to Observable

Observables have a method subscribe which is called with a callback function to get the values sent ( emit ) in an Observable.In Angular, it is used in components/directives, and especially in the router module, NgRx, and HTTP.

If we subscribe to a thread, it will remain open and will be called whenever it receives values from any part of the application, until it is closed by calling unsubscribe

@Component({...})export class AppComponent implements OnInit {subscription:SubscriptionngOnInit () {const observable = Rx.Observable.interval(1000);this.subscription= observable.subscribe(x => console.log(x));}}

In this implementation, we use an interval to send values every second.We subscribe to it to get the value sent, and our callback function writes the result to the browser console.

Now if the AppComponent is destroyed, for example, after exiting the component or using the destroy() method, we will still see the console log in the browser. This is because although the AppComponent has been destroyed, the subscription has not been canceled.

If the subscription is not closed, the callback function will be called continuously, which will lead to a serious memory leak and performance problems. In order to avoid leaks it is necessary to "unsubscribe" from Observable every time.

1. Using the unsubscribemethod

Any Subscription has a function unsubscribe() to free up resources and cancel execution of Observable. To prevent memory leaks, it is necessary to unsubscribeusing the method unsubscribe in Observable.

In Angular, you have to unsubscribe from an Observable when a component is destroyed. Fortunately, Angular has a hook. ngOnDestroy which is called before destroying a component, allowing developers to ensure that the memory is cleaned up, avoid hanging subscriptions, open ports, and other "legshots."

@Component({…})export class AppComponent implements OnInit, OnDestroy {subscription: SubscriptionngOnInit () {const observable = Rx.Observable.interval(1000);this.subscription= observable.subscribe(x => console.log(x));}ngOnDestroy() {this.subscription.unsubscribe()}}

We added ngOnDestroy into our AppComponent and called the unsubscribe on the Observable this.subscription When the AppComponent is destroyed (by clicking on the link, method destroy() etc.), the subscription will not hang, the interval will be stopped, and there will be no more console logs in the browser.

What if we have multiple subscriptions?

@Component({…})export class AppComponent implements OnInit, OnDestroy {subscription1$: Subscription;subscription2$: Subscription;ngOnInit () {const observable1$ = Rx.Observable.interval(1000);const observable2$ = Rx.Observable.interval(400);this.subscription1$ = observable.subscribe(x => console.log("From interval 1000" x));this.subscription2$ = observable.subscribe(x => console.log("From interval 400" x));}ngOnDestroy() {this.subscription1$.unsubscribe();this.subscription2$.unsubscribe();}}

AppComponent has two subscriptions and both are unsubscribed in the hook ngOnDestroy , preventing a memory leak.

You can also collect all subscriptions in an array and unsubscribe them in a loop :

@Component({…})export class AppComponent implements OnInit, OnDestroy {subscription1$: Subscription;subscription2$: Subscription;subscriptions: Subscription[] = [];ngOnInit () {const observable1$ = Rx.Observable.interval(1000);const observable2$ = Rx.Observable.interval(400);this.subscription1$ = observable.subscribe(x => console.log("From interval 1000" x));this.subscription2$ = observable.subscribe(x => console.log("From interval 400" x));this.subscriptions.push(this.subscription1$);this.subscriptions.push(this.subscription2$);}ngOnDestroy() {this.subscriptions.forEach((subscription) => subscription.unsubscribe());}}

The subscribe method returns an RxJS object of type Subscription. It represents a one-time resource. Subscriptions can be grouped using the add which will attach a child subscription to the current subscription. When a subscription is unsubscribed, all of its children are unsubscribed as well. Let’s try to rewrite our AppComponent:

@Component({…})export class AppComponent implements OnInit, OnDestroy {subscription: Subscription;ngOnInit () {const observable1$ = Rx.Observable.interval(1000);const observable2$ = Rx.Observable.interval(400);const subscription1$ = observable.subscribe(x => console.log("From interval 1000" x));const subscription2$ = observable.subscribe(x => console.log("From interval 400" x));this.subscription.add(subscription1$);this.subscription.add(subscription2$);}ngOnDestroy() {this.subscription.unsubscribe()}}

That’s how we’ll write off this.subscripton1$ and this.subscripton2$ At the moment of component destruction.

2. Using Async | Pipe

Pipe async subscribes to an Observable or Promise and returns the last value sent. When a new value is sent, pipe async checks this component for changes. If the component is destroyed, pipe async is automatically unsubscribed.

@Component({..., template: `<div>Interval: {{observable$| async}}</div>`})export class AppComponent implements OnInit {observable$;ngOnInit () {this.observable$= Rx.Observable.interval(1000);}}

When initialized, the AppComponent will create an Observable from the interval method. In the pattern. observable$ is passed to async It will subscribe to the observable$ and display its value in the DOM. It will also unsubscribe when the AppComponent is destroyed. Pipe async has a hook in its class ngOnDestroy , so it will be called when its view is destroyed.
Pipe async is very convenient to use, because it will subscribe to Observable and unsubscribe from them. And we don’t have to worry about unsubscribing in ngOnDestroy

3. Use of RxJS take* operators

RxJS contains useful operators that can be used declaratively to unsubscribe in our Angular project. One of them is the operators of the *take** family:

  • take(n)
  • takeUntil(notifier)
  • takeWhile(predicate)

take(n)
This operator emit-its the original subscription a specified number of times and exits. Most often a unit (1) is passed to take for subscription and exit.

This operator is useful if we want the Observable to pass a value once and then unsubscribe from the thread :

@Component({...})export class AppComponent implements OnInit {subscription$;ngOnInit () {const observable$= Rx.Observable.interval(1000);this.subscription$= observable$.pipe(take(1)).subscribe(x => console.log(x));}}

subscription$ will unsubscribe when the interval passes the first value.

Note : even if the AppComponent is destroyed, subscription$ will not unsubscribe until the interval passes the value. So it is still better to make sure that everything is written in the hook ngOnDestroy :

@Component({…})export class AppComponent implements OnInit, OnDestroy {subscription$;ngOnInit () {var observable$= Rx.Observable.interval(1000);this.subscription$ = observable$.pipe(take(1)).subscribe(x => console.log(x));}ngOnDestroy() {this.subscription$.unsubscribe();}}

takeUntil(notifier)
This operator emit-its values from the original Observable, as long as notifier does not send a completion message.

@Component({…})export class AppComponent implements OnInit, OnDestroy {notifier= new Subject();ngOnInit () {const observable$ = Rx.Observable.interval(1000);observable$.pipe(takeUntil(this.notifier)).subscribe(x => console.log(x));}ngOnDestroy() {this.notifier.next();this.notifier.complete();}}

We have an additional Subject for notifications, which will send a command to unsubscribe this.subscription We pipe-im Observable to takeUntil as long as we are subscribed. TakeUntil will emit-it interval messages until notifier does not unsubscribe observable$ It is most convenient to place notifier into a hook ngOnDestroy

takeWhile(predicate)
This operator will emit-it Observable values as long as they match the predicate condition.

@Component({...})export class AppComponent implements OnInit {ngOnInit () {const observable$ = Rx.Observable.interval(1000);observable$.pipe(takeWhile(value => value < 10)).subscribe(x => console.log(x));}}

We’re a observable$ with the takeWhile statement, which will send values as long as they are less than 10. If a value greater than or equal to 10 arrives, the operator will unsubscribe.
It is important to understand that the subscription observable$ will be open until the interval gives out 10. So, to be safe, we add a hook ngOnDestroy to unsubscribe from observable$ , when the component is destroyed.

@Component({…})export class AppComponent implements OnInit, OnDestroy {subscription$;ngOnInit () {var observable$ = Rx.Observable.interval(1000);this.subscription$ = observable$.pipe(takeWhile(value => value < 10)).subscribe(x => console.log(x)); } ngOnDestroy() { this.subscription$.unsubscribe(); } }

4. Using RxJS first

This operator is similar to the combined take(1) and takeWhile.

If it is called without a parameter, it emit-its the first value of the Observable and terminates. If it is called with a predicate function, it emit-its the first value of the original Observable that matches the condition of the predicate function, and terminates.

@Component({...})export class AppComponent implements OnInit {observable$;ngOnInit () {this.observable = Rx.Observable.interval(1000);this.observable$.pipe(first()).subscribe(x => console.log(x));}}

observable$ will end if the interval passes its first value.This means that we will only see 1 log message in the console.

@Component({...})export class AppComponent implements OnInit {observable$;ngOnInit () {this.observable$= Rx.Observable.interval(1000);this.observable$.pipe(first(val => val === 10)).subscribe(x => console.log(x));}}

Here first will not emit values until the interval passes 10 and then terminates observable$ We will only see one message in the console.

In the first example, if the AppComponent is destroyed before first gets the value from observable$ , the subscription will still be open until first receives the message.
Similarly, in the second example, if the AppComponent is destroyed before the interval gives a value that fits the operator condition, the subscription will still be open until the interval gives 10. Therefore, to be safe, we have to explicitly cancel subscriptions in the hook ngOnDestroy

5. Using the Decorator to Automate Unsubscribe

We are all human, we tend to forget. Most of the previous ways rely on the hook ngOnDestroy to make sure that the subscription is cleared before destroying the component. But we can forget to put them in ngOnDestroy , – maybe because of a deadline, or a nervous client who knows where you live…

In this case, we can use Decorators in our Angular projects to automatically unsubscribe all subscriptions in a component.

Here is an example of such a useful implementation :

function AutoUnsub() {return function(constructor) {const orig = constructor.prototype.ngOnDestroy;constructor.prototype.ngOnDestroy= function() {for(let prop in this) {const property = this[prop];if(typeof property.subscribe === "function") {property.unsubscribe();}}orig.apply();}}}

This one AutoUnsub is a decorator that can be applied to classes in our Angular project. As you can see, it retains the original hook ngOnDestroy and then creates a new one and connects it to the class it’s applied to. Thus, when the class is destroyed, the new hook is called. Its function looks through the class properties, and if it finds an Observable, it unsubscribes from it. Then it calls the original hook ngOnDestroy in the class, if there is one.

@Component({...})@AutoUnsubexport class AppComponent implements OnInit {observable$;ngOnInit () {this.observable$ = Rx.Observable.interval(1000);this.observable$.subscribe(x => console.log(x))}}

We apply it to our AppComponent and no longer worry about forgetting to unsubscribe from observable$ in ngOnDestroy , – the decorator will do it for us.

But this method has a downside:there will be errors if we have an Observable without a subscription in our component.

6. Use of tslint

Sometimes it can be useful to have a message from tslint to tell the console that our components or directives do not have a method declared ngOnDestroy We can add a custom rule to tslint to warn the console when lint or build is running that our components do not have a hook ngOnDestroy :

// ngOnDestroyRule.tsimport * as Lint from "tslint"import * as ts from "typescript";import * as tsutils from "tsutils";export class Rule extends Lint.Rules.AbstractRule {public static metadata:Lint.IRuleMetadata = {ruleName: "ng-on-destroy", description: "Enforces ngOnDestory hook on component/directive/pipe classes", optionsDescription: "Not configurable.", options: null, type: "style", typescriptOnly: false}public static FAILURE_STRING = "Class name must have the ngOnDestroyhook";public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {return this.applyWithWalker(new NgOnDestroyWalker(sourceFile, Rule.metadata.ruleName, void this.getOptions()))}}class NgOnDestroyWalker extends Lint.AbstractWalker {visitClassDeclaration(node: ts.ClassDeclaration) {this.validateMethods(node);}validateMethods(node: ts.ClassDeclaration) {const methodNames = node.members.filter(ts.isMethodDeclaration).map(m => m.name!.getText());const ngOnDestroyArr = methodNames.filter( methodName => methodName === "ngOnDestroy");if( ngOnDestroyArr.length === 0)this.addFailureAtNode(node.name, Rule.FAILURE_STRING);}}

If we have such a component without ngOnDestroy :

@Component({...})export class AppComponent implements OnInit {observable$;ngOnInit () {this.observable$ = Rx.Observable.interval(1000);this.observable$.subscribe(x => console.log(x));}}

Lint-ing an AppComponent will alert us to a missed hook ngOnDestroy :

$ ng lintError at app.component.ts 12: Class name must have the ngOnDestroy hook

Conclusion

Hanging or open unsubscribing can lead to memory leaks, bugs, unwanted behavior, or poor application performance. To avoid this, we looked at different ways to unsubscribe from Observable in Angular projects. And it’s up to you to decide which one to use in your particular situation.

You may also like