Reputation: 1208
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
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
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