Angular/RxJs - Calling next() on Observable in service after component was unsubscribe() cause ObjectUnsubscribedError

1.9k Views Asked by At

I have an Angular Service that defines a public Subject. Later under some condition, the service will call .next() on this Subject.

In addition, I have an Angular component that .subscribe() to this Subject in it's ngOnInit() method, and call .unsubscribe() in it's ngOnDestroy() method.

While both the service and the component are alive my code works fine. However, the user can click on a different tab in my app. Which cause my component to unload and call its ngOnDestroy() method, meaning the Observable was unsubscribed.

Starting from this point, the component no longer exists however the service is still alive - other components still use it. When the service calling .next() method after the component called .unsubscribe(), this error is thrown ObjectUnsubscribedError. ERROR Error: Uncaught (in promise): ObjectUnsubscribedError: object unsubscribed

In addition, later if the user goes back to the tab that contains the component, the ngOnInit() is executed and the re-subscription to this observable throws the same error again.

What am I doing wrong, how can I resolve these errors?

import { Subject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class StatusService {
  public statusChange$: Subject<number>;

  public constructor(...) {
    this.statusChange$ = new Subject<number>();
  }

  public someInternalLogic(someNumber: number): void {
    this.statusChange$.next(someNumber);
    // this will throw an error if StatusComponent call .unsubscribe()
  }
}

@Component({
  selector: 'app-status',
  templateUrl: './status.component.html',
  styleUrls: ['./status.component.scss']
})
export class StatusComponent implements OnInit, OnDestroy {
  public constructor(
    private _statusService: StatusService) {}

    public ngOnInit(): void {
      this._statusService.statusChange$.subscribe(value => this._onStatusChange(value));
  }

  public ngOnDestroy(): void {
    this._statusService.statusChange$.unsubscribe();
    // after this point if the StatusService calling .next() an error will be thrown
  }

  private _onStatusChange(value: number): void {
    // ....
  }
}

2

There are 2 best solutions below

0
On BEST ANSWER

You don't need to unsubscribe service subject in component. Instead create an subscription variable for service subject in your component and unsubscribe it on component destroy

subscription!: Subscription;

public ngOnInit(): void {
    this.subscription = this._statusService.statusChange$.subscribe(value => this._onStatusChange(value));
}

public ngOnDestroy(): void {
    this.subscription.unsubscribe();
}

private _onStatusChange(value: number): void {
    // ....
}
0
On

Another way to collectively unsubscribe on the OnDestroy event is the usage of the takeUntil operator. So you don't have to store each of your subscriptions, instead you can create a subject and trigger it when the component gets destroyed.

Imagine your service has multiple Subjects and your component subscribes to each of them. In this case you would have to store all of your subscriptions via class properties (or an array of subscriptions for that matter) and you would have to unsubscribe manually from each of them.

class myComponent {

  // create any subject which allows you to push into
  // when your component gets destroyed
  private onDestroy = new Subject<void>();

  public ngOnInit(): void {
    // Subscription #1
    this._statusService.statusChange_1$.pipe(
      tap(() => /* do something */),
      // takeUntil will listen to changes of the onDestroy subject
      // and whenever you trigger it by this.onDestroy.next() it will
      // automatically unsubsribe for you
      takeUntil(this.onDestroy)
    ).subscribe();

    // Subscription #2
    this._statusService.statusChange_2$.pipe(
      takeUntil(this.onDestroy)
    ).subscribe(value => {
      /* do something else */
      // you can put your logic into your subscribe method as well
      // like you did in your question
    });

    // Any number of more subscriptions...
    // ...
  }

  public ngOndestroy(): void {
    // Notify the observers to unsibscribe
    this.onDestroy.next();
  }
}

This approach works with any observable.