6

For instance, I'm currently calling unsubscribe from an Observable that is returned from Angular 2's HTTP module.

But I need to have some custom logic surrounding it.

Is it possible to add custom teardown logic to an already existing Observable, like the one returned from Angular 2's HTTP module?

Something along the lines of:

Observable.prototype.whenUnsubscribed(customTeardownLogic)
ccjmne
  • 9,333
  • 3
  • 47
  • 62
Nick Snick
  • 911
  • 1
  • 9
  • 16

5 Answers5

5

This might not be exactly what you want but it may help:

Suppose you have something like this (taken from the Hero Guide at Angular 2 website):

@Injectable()
export class HeroService {
    private heroesUrl = 'api/heroes';  // URL to web API

    constructor (private http: Http) {}
  
    getHeroes(): Observable<Hero[]> {
        return this.http
                   .get(this.heroesUrl)
                   .map(this.extractData);
    }

    private extractData(res: Response) {
        let body = res.json();
        return body.data || { };
    }
}

// Somewhere else in your code you do:
let subscription = service.getHeroes().subscribe(/* do stuff here */);

// ...and later on:
subscription.unsubscribe();

If you want to add some custom tear-down logic, you could wrap the Observable returned by Angular 2 with your own:

getHeroes(): Observable<Hero[]> {
    return Observable.create(
        //Let's provide a subscribe function
        (observer: any) => { 
            const subscription = this.http
                                     .get(this.heroesUrl)
                                     .map(this.extractData)
                                     .subscribe(observer);

            // Return a tear-down/unsubscribe function
            return () => {
                subscription.unsubscribe();
                this.myTearDownLogic();
            }
        }
     );
}
Andris
  • 5,853
  • 3
  • 28
  • 34
yms
  • 10,361
  • 3
  • 38
  • 68
  • This doesn't answer the question. The answer to the question is simply No. What is offered in this "answer" introduces unwanted and not asked for complexity. – Youp Bernoulli Feb 17 '23 at 20:07
5

To answer the exact question asked, no, you cannot add teardown logic to an existing observable.

However, there is teardown logic for an observable (which you may or may not have control over) and teardown logic for an observer - i.e., your subscription - which is yours to manage.

It sounds like you want to use the latter teardown logic using the add() method:

anyObservable.subscribe(function() {

  // my observer code

}).add(function() {

  // my teardown logic

});

Above I've demonstrated using a function for teardown logic, but a subscription can also be sent, which provides an easy way to unsubscribe any number of other subscriptions when this one is closed (completed/error).

Be aware that if the observable is in a closed state when your teardown logic is added, it will be executed immediately.

uɥƃnɐʌuop
  • 14,022
  • 5
  • 58
  • 61
  • Note: You should handle the error callback in that subscribe() as well, otherwise you get uncaught error once your anyObservable errors. – Marc J. Schmidt May 01 '19 at 20:50
2

TL;DR:

Assuming a function teardown() that holds your teardown logic an original: Observable that you need to enhance:

new Observable(
  sub => original.subscribe(sub).add(() => teardown())
)

A simple, fool-proof one-liner

This section merely explains the code in the TL;DR: above.

There actually is a way:
Simply create a new Observable with your additional TeardownLogic and let its subscriptions actually subscribe to your original one!

const original: Observable<T> = // [...]
return new Observable(subscription => {
  // Add custom teardown logic
  subscription.add(() => teardown());

  // Delegate to the original `Observable`
  original.subscribe(subscription);
});

You may also simply provide the TeardownLogic callback as a result of the subscription handler you pass to the constructor of the new Observable:

const original: Observable<T> = // [...]
return new Observable(subscription => {
  // Delegate to the original `Observable`
  original.subscribe(subscription);

  // Define the teardown logic here
  return () => teardown();
});

Also

Don't hesitate to actually inline the original Observable if you may!

new Observable(
   sub => fromEvent(document, 'click').subscribe(sub).add(() => teardown())
//        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//         your `original` Observable
)
ccjmne
  • 9,333
  • 3
  • 47
  • 62
1

A bit late to the party, but here are my two cents: create a reusable operator!

It comes handy with Angular Async pipes, or services for multiple components.

teardown.ts

export function teardown<T>(afterEachCb: () => void, afterAllCb?: () => void) {
  let subscriberCount = 0;
  return (source: Observable<T>): Observable<T> => {
    return new Observable((subscriber) => {
      const subscription = source.subscribe(subscriber);
      subscriberCount += 1;

      return () => {
        subscription.unsubscribe();
        subscriberCount -= 1;
        afterEachCb();
        if (subscriberCount === 0) {
          afterAllCb();
        }
      };
    });
  };
}

And you can use this as follows:

heroes.component.ts (excerpt)

this.heroes$ = this.heroService.getHeroes().pipe(
  teardown(() => {
    // custom teardown logic here
  })
);

heroes.component.html (excerpt)

<app-hero [hero]="hero" *ngFor="let hero of heroes$ | async"></app-hero>

Disclaimer: I have not tested this in all scenarios. I use this to provide a WakelockService which automatically releases the wake lock when no more component requests it (via subscription).

Hodossy Szabolcs
  • 1,598
  • 3
  • 18
  • 34
0

The answer is simply NO.

All the other answers do not offer a way of injecting teardown logic in an already existing/created Observable out of your control (for example the ones that can be susbscribed to from the Angular HttpClient).

How can you ever add/inject the teardownlogic for an Observable created in a module you don't control?

The other answers offer ways of adding some logic to be executed when you unsubscribe from the observable but don't manage to, in any way, "influence" the tear-down-logic of the original observable.

Tear-down-logic can only be provided when the observable is created with new Observable(...) or Observable.create(...)

Youp Bernoulli
  • 5,303
  • 5
  • 39
  • 59