yoonjesung
yoonjesung

Reputation: 1208

Angular2 routing and Cancelling Observables mid stream

I have an Angular Http get request that will navigate the route to one of three routes depending on the data returned. Sample code below:

SomeComponent:

constructor(
   private someService: SomeService,
   private router: Router
) { }

ngOnInit() {
   this.someService.getSomeData().subscribe(
      () => {
         console.log("getSomeData() did not route to a new link! Routing to a 3rd link");
         this.router.navigate(['home']);
      },
      () => {
         console.log("Some error occurred.");
      });

SomeService:

constructor(
   private router: Router,
   private someOtherService: SomeOtherService
) { }

getSomeData() {
   return this.someOtherService.getSomeOtherData().map(data => {
      if (data === 'someValue') {
         console.log("The data was 'someValue'");
         this.router.navigate(['route1']);
      }
      else if (data == 'someOtherValue') {
         console.log("The data was 'someOtherValue'");
         this.router.navigate(['route2']);
      }
   });
}

SomeOtherService:

constructor(
   private http: Http
) { }

getSomeOtherData() {
   return this.http.get(this.getDataUrl) {
      .map((res: Response) => {
         console.log("Sending over the data back to SomeService");
         let body = res.json();
         return body.data;
      })
      .catch((err: Response) => {
         console.log(err);
         return Observable.throw(err);
      });
   }
}

The expected behavior is that after receiving the data from SomeOtherService, the Router will navigate to either route1 or route2 depending on the data (which I thought I read somewhere would stop the Observable stream? Maybe I misread). If the data doesn't match, then the Observable stream continues to SomeComponent which then navigates to home.

The actual behavior is that the Router will initially route to route1 or route2 but since the stream continues, the Router then finally routes to home.

So my question is, if you subscribe to an rxjs Observable, is there a way to cancel/unsubscribe to the Observable mid-stream? I thought that navigating to another route would cancel the observable but this didn't seem to work for me.

I tried unsubscribing within the map method with interesting(ly bad) results. I also tried looking at other Observable methods but being new to rxjs I wasn't sure which method would be most appropriate (I'm really only familiar with map, catch, mapTo, and (sort of) switchMap at this point).

Upvotes: 0

Views: 1213

Answers (2)

meriton
meriton

Reputation: 70574

The expected behavior is that after receiving the data from SomeOtherService, the Router will navigate to either route1 or route2 depending on the data (which I thought I read somewhere would stop the Observable stream? Maybe I misread). If the data doesn't match, then the Observable stream continues to SomeComponent which then navigates to home.

Not quite :-)

Navigating to another view completes all Observables provided by the old ActivatedRoute. The Observables you created are not obtained from an ActivatedRoute (nor tied to an ActivatedRoute by other means), and will therefore exist independent of which route is active.

Also, router.navigate(...) is an asynchronous operation, meaning it only schedules a navigation, but does not wait for navigation to complete. That is, even if your Observables were tied to the ActivatedRoute, they would still be alive, and their Observers notified.

You can fix this using:

constructor(
   private router: Router,
   private someOtherService: SomeOtherService
) { }

getSomeData() {
   return this.someOtherService.getSomeOtherData().map(data => {
      if (data === 'someValue') {
         console.log("The data was 'someValue'");
         return ['route1'];
      }
      else if (data == 'someOtherValue') {
         console.log("The data was 'someOtherValue'");
         return ['route2'];
      } else {
         return null;
      }
   });
}

and use it like:

this.someService.getSomeData().subscribe({
  next: (route) => {
     this.router.navigate(route || ['home']);
  },
  error: (err) => {
     console.log("Some error occurred.", err);
  }
});

BTW, to better understand what an Observable is, you may want to read the ReactiveX documentation.

Upvotes: 2

Jager
Jager

Reputation: 103

There are multiple ways to unsubscribe from an observable

The most straightforward way is to keep an handle for the subscription and unsubscribe for example in the OnDestroy method.

import { Subscription } from 'rxjs/Rx'

...

private sub: Subscription;

constructor(
   private someService: SomeService,
   private router: Router
) { }

ngOnInit() {
   this.sub = this.someService.getSomeData().subscribe(..)
}

ngOnDestroy() {
    if (this.sub) this.sub.unsubscribe();
}

Also doing the navigate in the map() method is a bit strange. The map() operator is used to transform an observable stream to another stream, it is not an endpoint. So usually you'd return something like someObs.map(s => s.name).subscribe(). If you only want to do something you could use the do() operator instead.

But in your case if data === 'someValue' you would first navigate to route1 in the map operator. Then the subscription callback (next()) is fired and the router navigates to 'home'.

To get the functionality you described you could do it as such:

SomeComponent:

constructor(
   private someService: SomeService,
   private router: Router
) { }

ngOnInit() {
   this.someService.getSomeData().subscribe({
      next: () => {
         console.log("getSomeData() did not route to a new link! Routing to a 3rd link");
         this.router.navigate(['home']);
      },
      complete: () => {},
      error: (err) => {
         console.log("Some error occurred.", err);
      }
    });

SomeService:

constructor(
   private router: Router,
   private someOtherService: SomeOtherService
) { }

getSomeData() {
   return Observable.create(observer => {
    this.someOtherService.getSomeOtherData().first().subscribe(data => {
      if (data === 'someValue') {
         console.log("The data was 'someValue'");
         this.router.navigate(['route1']);
      }
      else if (data == 'someOtherValue') {
         console.log("The data was 'someOtherValue'");
         this.router.navigate(['route2']);    
      } else {
         // none of the options above. call next() on the observer so 
         // it will route to 'home'
         observer.next();
      }
      observer.complete();

   });
});


}

Upvotes: 1

Related Questions