starfighter104
starfighter104

Reputation: 185

Angular 7 Rxjs - In-Flight HTTP request not cancelling for subscription

I'm having trouble understanding why unsubscribing from a rxjs subscription with an added step does not cancel a in-flight http request.

Take a look at: https://stackblitz.com/edit/angular5-http-example-nx8fza

You'll notice in the network tab for chrome, one of the clicks cancels requests and the other does not.

 getJoke(): void {
    this.jokeSub = this.api.getData()
      .subscribe(data => this.joke = data);

    this.jokeSub.unsubscribe();
  }

  // This method does not cancel requests in flight because of the .add step.
  getJoke2(): void {
    this.jokeSub2 = this.api.getData()
      .subscribe(data => this.joke = data).add(console.log("Added step"));

    this.jokeSub2.unsubscribe();
  }

cancel request

Side Note: I came across this when I setting up an http interceptor in Angular 7 with rxjs 6. I couldn't cancel requests when my interceptor had any logic in the pipe. I think if can figure out why its not working here, I can answer my more specific question.

Upvotes: 1

Views: 843

Answers (2)

kos
kos

Reputation: 5364

The .add method adds teardown logic and returns a new subscription for that teardown logic. That new subscription is added as an inner subscription to the initial one. So these subscription have parent-child relations, and if you unsubscribe from parent — child is also disposed.

Though unsubscribing from children wont complete the parent.

Heres an explanation from the docs on add method

Returns the Subscription used or created to be added to the inner subscriptions list. This Subscription can be used with remove() to remove the passed teardown logic from the inner subscriptions list.

Heres a playground for that. Try commenting out some timeouts:

const { timer } = rxjs;
const { finalize, take } = rxjs.operators;

const startedAt = Date.now();

const a = timer(0, 300).pipe(
    finalize(createTeardown('a')),
    take(10)
  )
  .subscribe(console.log);

const b = a.add(createTeardown('b'));
const c = a.add(createTeardown('c'));

// Try commenting out some of these
setTimeout(()=>a.unsubscribe(), 1000);
setTimeout(()=>b.unsubscribe(), 1000);
setTimeout(()=>c.unsubscribe(), 1000);


function createTeardown(name) {
  return () => console.log(name, Date.now() - startedAt);
}
<script src="https://unpkg.com/[email protected]/bundles/rxjs.umd.min.js"></script>

To fix your particular code example, you'll need to store original subscription separately:

getJoke2(): void {
  this.jokeSub2 = this.api.getData()
    .subscribe(data => this.joke = data); // parent subscription (a)

  this.jokeSub2
    .add(() => console.log('Child teardown logic')); // child subscription (b)

  this.jokeSub2.unsubscribe();
}

NOTE: this basically duplicates AlesD's answer. Joining this into one answer for transparency

Hope this helps

Upvotes: 1

Aleš Doganoc
Aleš Doganoc

Reputation: 12052

As already the other answer explains this is how rxjs works I add also how you should change your code to make it work like the first sample.

getJoke2(): void {
    this.jokeSub2 = this.api.getData()
      .subscribe(data => this.joke = data);
    this.jokeSub2.add(console.log("Added step"));

    this.jokeSub2.unsubscribe();
}

Also a link to a fork of your StackBlitz where you can see it works.

Upvotes: 1

Related Questions