BlackHoleGalaxy
BlackHoleGalaxy

Reputation: 9672

What is the best way to unsubscribe infinite observables in an Angular service

In our Angular websocket service, in order to tell the backend we are still listening, we have to send kind of keep alive signal.

Here is the way we proceed:

@Injectable()
export class WebsocketService {
  private ws: CustomWebSocket;

  constructor() {
    this.ws = new CustomWebSocket('ws://localhost');
    this.ws.onOpened(() => {
      console.info('WS connected');
      this.keepAlive();
    });
  }

  public keepAlive() {
    Observable.interval(10000).subscribe((tick: number) => this.ws.send('keep-alive'));
  }
}

1 / This service is injected in our main app.module.ts. Do we have to unsubscribe the interval manually or it's safe to allow this infinite observable sequence?

2 / As a general question if we have to unsubscribe, what is the proper way to do it since the subscription is within a service? We don't have the ngOnDestroy lifecycle hook so how to do it properly?

Upvotes: 1

Views: 1151

Answers (2)

Ingo Bürk
Ingo Bürk

Reputation: 20053

Do we have to unsubscribe the interval manually or it's safe to allow this infinite observable sequence?

Typically, if the service is meant to live for the entire lifetime of the app, you wouldn't have to bother since the browser will clean up for you. However, there are some things to consider:

  • If your app is only loaded inside a bigger app, this will no longer be true.
  • If you ever change your app to only provide the service on some sub-component, you have to know about implementation details of the service and remember to adjust it to clean up after itself in order to avoid memory leaks.

Following the principle of least surprise, a service should clean up after itself when it gets destroyed, so I absolutely would recommend doing so.

We don't have the ngOnDestroy lifecycle hook so how to do it properly?

This is a popular statement, but it is wrong. ngOnDestroy is also called on services. Straight from the docs:

Lifecycle hook that is called when a directive, pipe or service is destroyed.

So you absolutely can do this:

@Injectable()
export class WebsocketService implements OnDestroy {
  private ws: CustomWebSocket;
  private destroy$ = new Subject();

  constructor() {
    this.ws = new CustomWebSocket('ws://localhost');
    this.ws.onOpened(() => {
      console.info('WS connected');
      this.keepAlive();
    });
  }

  public keepAlive() {
    Observable.interval(10000)
      .takeUntil(this.destroy$)
      .subscribe((tick: number) => this.ws.send('keep-alive'));
  }

  public ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

Upvotes: 3

mtsuggs
mtsuggs

Reputation: 91

The problem with the service is that there isn't an on destroy method for you to unsubscribe from and it does need to be unsubscribed, only http requests are self closing (the http service does this for you).

I would have your WebsocketService return a cold observable to your main app component.

Create a placeholder for your observable in your main app component IE: ngUnsubscribe: Subject = new Subject();

Subscribe to your observable from your service. IE: WebsocketService.keepAlive().takeUntil(this.ngUnsubscribe).subscribe();

In the onDestroy event in your main app component I would then call this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();

Upvotes: 0

Related Questions