subscribing to nested observables - rxjs

I'm creating an observable that needs to perform a few steps, each step is reliant on the previous - for example something like get an ID, use that ID to get a user, use that user to get an article. Each step makes a call to the database, which in turn returns its own observable. I'm really not sure how to deal with nested observables without subscribing to them, or ensure that when the outer observable is subscribed to eventually all the nested observables are subscribed to as well.
Example:
newArticle(article: Article): Observable<any> {
// db api returns an observable of requests
return this.db.get(`select id from user_ids where name = ${article.name}`).pipe(map((id) => {
return this.db.get(`select user from users where user_id = ${id}`).pipe(map((user) => {
return this.db.put(`insert into articles(user_name, title, content) values (${user.name}, ${article.title}, ${article.content});
}));
}));
}
Using this current method doesn't work, when the observable returned from newArticle is subscribed to only the outermost observable is subscribed and executed it seems. Is there something I'm missing in the way to deal with nested observables? I'm relatively new to rxjs and having a hard time completely grasping observables. Any help would be greatly appreciated.

In RxJS, the solution to avoid nested subscriptions is to use a "Higher Order Mapping Operator" (switchMap, mergeMap, concatMap, exhaustMap). These operators will subscribe to an "inner observable" for you and emit its emissions.
I won't go into the differences between these operators here, but switchMap will work for your case.
Your sample code is very close; you can basically use switchMap instead of map.
function newArticle(article: Article) {
return this.db.get(`select id from user_ids where name = ${article.name}`).pipe(
switchMap(id => this.db.get(`select user from users where user_id = ${id}`)),
switchMap(user => this.db.put(`insert into articles(user_name, title, content) values (${user.name}, ${article.title}, ${article.content}`))
);
}
You may find the code easier to follow if you define separate functions with specific purpose:
function getUserIdForArticle(article: Article): Observable<string> {
return this.db.get(`select id from user_ids where name = ${article.name}`);
}
function getUser(id: string): Observable<User> {
return this.db.get(`select user from users where user_id = ${id}`);
}
function storeArticle(user: User, article: Article): Observable<Article> {
return this.db.put(`insert into articles(user_name, title, content) values (${user.name}, ${article.title}, ${article.content}`);
}
function newArticle(article: Article) {
return getUserIdForArticle(article).pipe(
switchMap(id => getUser(id)),
switchMap(user => storeArticle(user, article))
);
}

Related

Rxjs return an Observable from inner observable

I have an outer observable that i use with its result in the inner observable
and that I need to return the result from the inner observable
In the following example, I need to return the result allPersons from the second observable
the result from that function is Observable I want the it will return Observable
getAllPerson(): Observable<Data1[]> {
return this.dataService.getIds().subscribe(
(ids) => {
return this.dataService.getPersons().pipe(
map((allPersons) => {
console.log(ids);
//filter persons according to ids
return allPersons;
})
})
);
}
Also tried: and get
Argument of type 'Observable' is not assignable to parameter of type 'OperatorFunction<any, any>'.
getAllPerson(): Observable<any> {
return this. dataService.getIds().pipe(
switchMap((data) => {
this.dataService.getPersons().subscribe(
(allPersons) => {
console.log(ids);
//filter persons according to ids
return allPersons;
})
})
);
}
It's going to be somthing like that :
function getAllPerson(): Observable<Data1[]> {
return this.dataService.getIds().pipe(
switchMap((ids) => {
return this.dataService.getPersons().pipe(
map((allPersons) => {
return allPersons.filter(...); //filter persons according to ids
})
);
})
);
}
And subscribe the whole thing.
Okay, I see you're using TypeScript (nice), but there's a type error in the first two lines of your code.
getAllPerson(): Observable<Data1[]> {
return this.dataService.getIds().subscribe( /*more code here */ );
The type check is going to look at this is complain. It will say something like, "Denotationally, you've declared that this function returns an Observable object. By inference, I can see you're returning a Subscription object. As far as I can tell, Observable objects and subscription objects cannot be unified. This is a type error.
It's right.
The issue is that once you subscribe to an observable, you're no longer in RxJS land. You're left with an imperative way to end an observable, but you're done dealing with observable.
Think about subscribing as your way to exist the RxJS library. So if operators like of, from, fromEvent, new Subject, new BehaviorSubject, ect are ways to enter the RxJS library, then subscribe, lastValueFrom, firstValueFrom, behaviorSub.value, etc are ways to exit the RxJS library.
So how to avoid that dreaded subscribe. This is where RxJS hiher order operators come in. They let you chain, combine, merge, etc streams
for Example:
function getAllPerson(): Observable<Data1[]> {
return this.dataService.getIds().pipe(
switchMap(ids => this.dataService.getPersons().pipe(
map(allPersons => allPersons.filter(/* filter code ...*/))
))
);
}

What is the purpose of RxJs Subscription and unsubscribe when it not needed?

I have seen the following code in an angular application. In the code, a Subscription is being used to subscribe and unsubscribe. I don't understand why a Subscription is used. Is there any benefit in using this pattern?
public routeChangeSub$: Subscription;
this.routeChangeSub$ = this.route.firstChild.paramMap
.subscribe((map: ParamMap) =>
this.getRouteParams(map));
this.routeChangeSub$.unsubscribe();
getRouteParams(map: ParamMap): number {
const characterId = map.get('id');
let id: number = null;
if (characterId) {
this.character$ = this.store.pipe(
select(selectCharacterById, { id: characterId })
);
id = parseInt(characterId, 10);
}
return id;
}
Update:
How this can be different from
this.route.firstChild.paramMap
.subscribe((map: ParamMap) =>
this.getRouteParams(map));
getRouteParams(map: ParamMap): number {
const characterId = map.get('id');
let id: number = null;
if (characterId) {
this.character$ = this.store.pipe(
select(selectCharacterById, { id: characterId })
);
id = parseInt(characterId, 10);
}
return id;
An observable (which is the type of route.firstChild.paramMap) will not emit anything unless something is subscribed to it.
In this file, the author explicitly subscribes to paramMap to trigger a state change by calling getRouteParams(). Then they immediately unsubscribe. If they didn't unsubscribe, the subscription will continue to run which may cause state issues and memory leaks.
A far simpler solution is to use the take(1) operator. This will take an emitted value (the 1 is the number of values to take) then will send a complete signal to the subscription. This causes the subscription to automatically unsubscribe.
this.route.firstChild.paramMap.pipe(
take(1),
tap(map=>this.getRouteParams(map))
).subscribe();
Because we're using take(), we don't need to assign the subscription to a property. The observable will emit one value, and will invoke getRouteParams() before unsubscribing.
Note: If you're not aware, tap() is the operator to use if you want to apply an effect to any state property outside of the observable.

RxJS test equality of two streams regardless of order

RxJS provides the sequenceEqual operator to compare two streams in order. How would one go about testing equality of two streams regardless of order?
Pseudocode:
//how do we implement sequenceEqualUnordered?
from([1,2,3]).pipe(sequenceEqualUnordered(from([3,2,1]))).subscribe((eq) =>
console.log("Eq should be true because both observables contain the same values")
)
In my particular use case I need to wait until a certain set of values has been emitted or error but I don't care what order they're emitted in. I just care that each value of interest is emitted once.
Here's my solution:
import { Observable, OperatorFunction, Subscription } from 'rxjs';
export function sequenceEqualUnordered<T>(compareTo: Observable<T>, comparator?: (a: T, b: T) => number): OperatorFunction<T, boolean> {
return (source: Observable<T>) => new Observable<boolean>(observer => {
const sourceValues: T[] = [];
const destinationValues: T[] = [];
let sourceCompleted = false;
let destinationCompleted = false;
function onComplete() {
if (sourceCompleted && destinationCompleted) {
if (sourceValues.length !== destinationValues.length) {
emit(false);
return;
}
sourceValues.sort(comparator);
destinationValues.sort(comparator);
emit(JSON.stringify(sourceValues) === JSON.stringify(destinationValues));
}
}
function emit(value: boolean) {
observer.next(value);
observer.complete();
}
const subscriptions = new Subscription();
subscriptions.add(source.subscribe({
next: next => sourceValues.push(next),
error: error => observer.error(error),
complete: () => {
sourceCompleted = true;
onComplete();
}
}));
subscriptions.add(compareTo.subscribe({
next: next => destinationValues.push(next),
error: error => observer.error(error),
complete: () => {
destinationCompleted = true;
onComplete();
}
}));
return () => subscriptions.unsubscribe();
});
}
As many of RxJS operators have some input parameters and as all of them return functions, sequenceEqualUnordered also has some input parameter (mostly the same as Rx's sequenceEqual operator) and it returns a function. And this returned function has the Observable<T> as the source type, and has Observable<boolean> as the return type.
Creating a new Observable that will emit boolean values is exactly what you need. You'd basically want to collect all the values from both source and compareTo Observables (and store them to sourceValues and destinationValues arrays). To do this, you need to subscribe to both source and compareTo Observables. But, to be able to handle subscriptions, a subscriptions object has to be created. When creating a new subscriptions to source and compareTo, just add those subscriptions to subscriptions object.
When subscribing to any of them, collect emitted values to appropriate sourceValues or destinationValues arrays in next handlers. Should any errors happen, propagate them to the observer in error handlers. And in complete handlers, set the appropriate sourceCompleted or destinationCompleted flags to indicate which Observable has completed.
Then, in onComplete check if both of them have completed, and if they all are, compare the emitted values and emit appropriate boolean value. If sourceValues and destinationValues arrays don't have the same lengths, they can't equal the same, so emit false. After that, basically sort the arrays and then compare the two.
When emitting, emit both the value and complete notification.
Also, the return value of function passed to the new Observable<boolean> should be the unsubscribe function. Basically, when someone unsubscribes from new Observable<boolean>, it should also unsubscribe from both source and compareTo Observables and this is done by calling () => subscriptions.unsubscribe(). subscriptions.unsubscribe() will unsubscribe from all subscriptions that are added to it.
TBH, I haven't wrote any tests for this operator, so I'm not entirely sure that I have covered all edge cases.
My first idea. Use toArray on both then zip them together finally sort and compare results?

Chaining HttpClient calls in Angular 6

I think I have read 100+ posts on the topic, and I still cannot figure out how to chain two HttpClient calls using rxjs in Angular 6.
Let's say I have a service with that signature:
GeoService {
getState(): Observable<string> {
return this.http.get<string>(stateURL);
}
getCities(state: string): Observable<string[]> {
return this.http.get<string[]>(citiesURL + state);
}
}
I can't for the life of me figure out how to obtain both the state and the corresponding list of cities in my component:
import { Observable } from 'rxjs';
import { map, flatMap, mergeMap, filter, switchMap } from 'rxjs/operators';
...
ngOnInit() {
this.svc.getState().
pipe(map((state) => {
this.state = state;
return this.svc.getCities(state);
}),
mergeMap((cities) => this.cities = cities))
).
subscribe(console.log('done'));
The code above in one of my 20 random attempts at combining pipe/map/mergeMap/subscribe in every way I could think of... a working example would be really really appreciated :)
Thanks!
Edit: None of the "possible duplicate" posts contain an actual example that works
The 21st attempt would have been correct ;-)
this.svc.getState().
pipe(mergeMap((state) => {
this.state = state;
return this.svc.getCities(state);
}),
tap((cities) => this.cities = cities)))
.subscribe(() => console.log('done'));
The chained Observable goes inside mergeMap. You can think of it as:
First, map the incoming notifaction to an Observable, then merge the resulting "inner" Observable into the "outer" Observable
Also, use tap instead of map if you intend to change an outside state.
You were almost there:
this.svc.getState().
pipe(
mergeMap((state) => {
return this.svc.getCities(state).pipe(map(cities => {
return { state: state, cities: cities }
}));
}),
).subscribe(stateAndCities => console.log(stateAndCities));
I advise you to read this article:
https://blog.strongbrew.io/rxjs-best-practices-in-angular/#using-pure-functions
It also explains why you shouldnt interact with global variables in rxjs operators.
You can do something like this
this.svc.getState().pipe(
tap(state=>this.state=state),
switchMap(this.svc.getCities))
.subscribe(cities=>{
//got the cities
})
the map operator is here to transform the emited value, but the tap operator is used to do something without modifying emited value of the observable.
note that switchMap(this.svc.getCities) is equivalent to switchMap(state=>this.svc.getCities(state)

Should I unsubscribe from this Observable?

Say I have the following method used to retreive all the "year" from an array of object and then get the distinct value:
public getYears(): Observable<number[]>
{
return Observable.from(this.payments.map(p => p.year)).distinct().toArray();
}
And elsewhere in my code, I use it like this:
this.getYears().subscribe(years => this.yearsRefinement = years);
Is it enough to do this or is it better to do:
let sub = this.getYears().subscribe(years => this.yearsRefinement = years);
sub.unsubscribe();
I really struggle knowing when I have to unsubcribe or not from an observable. From what I understand, this observable is "finite", so once I get the value, it's completed, so it's not required to unsubscribe from it. Is it correct?

Resources