RxJS - Executing observables in sequence - rxjs

I'm trying to run several observables in sequence, while saving their values, but am having a hard time wrapping my head around the right way to do this.
An abstract outline of what I'm trying to do is to create a "FruitBasket", after first creating apples, and then creating pears, in order.
createApples: Observable<Apple[]> {
// creates apples...
}
createPears: Observable<Pear[]> {
// creates pears...
}
createFruitBasket(apples: Apple[], pears: Pear[]): Observable<FruitBasket>
{
// create a fruit basket
}
I would like the creation of apples to complete before attempting the creation of pears, and the creation of pears should complete before attempting the creation of the fruit basket.
Using async/await notation, the desired result would be:
makeABasket() {
let apples = await createApples();
let pears = await createPears();
let fruitBasket = await createFruit(apples, pears);
}
As a first attempt, I've come up with the following:
makeABasket() {
let apples: Apple[] = [];
let pears: Pear[] = [];
let basket: FruitBasket;
this.createApples().subscribe((newApples) => {
apples = apples.concat(newApples);
this.createPears().subscribe((newPears) => {
pears = pears.concat(newPears);
this.createFruitBasket(apples, pears).subscribe((newBasket) => {
basket = newBasket;
});
});
});
}
I know there is a better way to do this, using RxJS operators, but I can't seem to settle upon the right way. Note that I don't want to use forkJoin, as I want the creation functions to run sequentially, not in parallel.
Thanks in advance for any suggestions.

Your makeABasket code could look something like this
makeABasket() {
return this.createApples().pipe(
concatMap(apples => this.createPears().pipe(
map(pears => {
return {apples, pears};
})
),
concatMap(({apples, pears}) => this.createFruitBasket(apples, pears)).pipe(
map(pears => {
return {apples, pears, basket};
})
)
)
}
With such an implementation makeABasket would return an Observable<{apples: Apple[], pears: Pear[], basket: Basket}>.
Whoever needs to used apples, pears and basket can subscribe to this Observable like this
makeABasket().subscribe(
({apples, pears, basket}) => {
// do stuff with apples, pears and basket
}
)

to run things in parallel you need the concat combination function.
its doc: https://www.learnrxjs.io/learn-rxjs/operators/combination/concat
simply do
concat( // <- creates sequence.
this.createApples(), // <- first one.
this.createPears(), // <- once apple has been completed executes this one.
).pipe(
scan((result, item) => [...result, item], []), // <- accumulating tuple
skip(1), // <- apples aren't enough, skipping 1st emit.
concatMap(([apples, pears]) => this.createFruitBasket(apples, pears)), // <- now we can set the basket.
).subscribe();

Related

How to create a method that returns Observable that emits result of 2 Promises that need to be executed one after another?

I asked a question
Is Observable from chained promises equivalent of observables created with from and chained with concatMap?
on totally false premises. It seems that neither of my solutions had nothing to do with my intention.
I created a method that returns Observable and calls 2 methods returning Promise. I tried 2 ways:
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
return from(this.db.selectionItemInfos.clear().then(() => {
return this.db.selectionItemInfos.bulkAdd(itemInfos);
}));
}
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
const clear$ = from(this.db.selectionItemInfos.clear());
const bulkAdd$ = from(this.db.selectionItemInfos.bulkAdd(itemInfos));
return clear$.pipe(concatMap(() => bulkAdd$))
}
the use will be:
myService.setItemInfos(itemInfos).subsribe(count => {
console.log(`Cleared the table 1st and then added ${count} new items`);
});
I thought from both versions that:
table clear is execution is finished when bulkAdd starts
when bulkAdd is finished i get the count from that in subscribe
How this should really be done? Or can it be done?
This is (from what I can tell here), how I would do it.
In general, defer (or any higher-order operator) is a better way to create an observable from a promise. Defer lets you take the eager evaluation semantics of promises and turn them into the lazy evaluation semantics of observables.
Then all the usual observable operators and such will function as expected.
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
const clear$ = defer(() => this.db.selectionItemInfos.clear());
const bulkAdd$ = defer(() => this.db.selectionItemInfos.bulkAdd(itemInfos));
return concat(clear$, bulkAdd$);
}
Update 1:
So I think I might know what you're after. This isn't really idiomatic RxJS since it's such an interleaving mix of declarative, imperative style of code. Even so, this should work? I haven't tested it fully, but some tinkering and I think this should do what you're after.
There's most assuredly a better way to accomplish the same thing, but without seeing the bigger picture of what you're after, it's hard to say.
interface Tagged<T> {
payload: T,
tag: number
}
class abitraryClass{
private setItemInfoSub: Subject<Tagged<IItemInfo[]>>;
private processItemInfo: Observable<Tagged<number>>;
private itemInfoTag = 0;
constructor(){
this.setItemInfoSub = new Subject<Tagged<IItemInfo[]>>();
this.processItemInfo = this.setItemInfoSub.pipe(
concatMap(({tag, payload: itemInfos}) => this.db.selectionItemInfos.clear().pipe(
ignoreElements(),
concatWith(defer(() => this.db.selectionItemInfos.bulkAdd(itemInfos))),
map(response => ({
payload: response,
tag
}))
)),
shareReplay(1)
);
// Make the processing pipeline live at all times.
this.processItemInfo.subscribe();
}
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
const myTag = this.itemInfoTag++;
this.setItemInfoSub.next({
payload: itemInfos,
tag: myTag
});
return this.processItemInfo.pipe(
filter(({tag}) => tag == myTag),
map(({payload}) => payload)
);
}
}

is there a Better way rather than to chain subscribe inside a subscribe with an if condition

Is there a better way to re-write this code and avoid chaining of subscriptions ?
Why am I chaining? because I need to the output of source1$ in child subscriptions
And also I have if conditions because I want to call child subscriptions conditionally
PS i checked solution in this post
Here is the stackblitz link and code
import { from } from 'rxjs';
//emit array as a sequence of values
const source1$ = from([1]);
const source2$ = from([2]);
const source3$ = from([3]);
const useCond1 = true; // this is dynamic can be false too
const useCond2 = true; // this is dynamic can be false too
source1$.subscribe(val => {
if (useCond1) {
source2$.subscribe(() => {
console.log('val from source1 in source2', val);
});
}
if (useCond2) {
source3$.subscribe(() => {
console.log('val from source1 in source3', val);
});
}
});
Not sure, but it seems that you need switchMap or mergeMap and iif
from rxjx doc:
import { fromEvent, iif, of } from 'rxjs';
import { mergeMap, map, throttleTime, filter } from 'rxjs/operators';
const r$ = of(`I'm saying R!!`);
const x$ = of(`X's always win!!`);
fromEvent(document, 'mousemove')
.pipe(
throttleTime(50),
filter((move: MouseEvent) => move.clientY < 210),
map((move: MouseEvent) => move.clientY),
mergeMap(yCoord => iif(() => yCoord < 110, r$, x$))
)
.subscribe(console.log);
Yes, there is a better way!
RxJS provides many different operators and static functions for combining, filtering, and transforming observables. When you use what the library provides, you do not need to have nested subscriptions.
In general, I find it simpler to not do any logic at all inside the subscribe, but rather design observables that emit the exact data that is needed.
A simplistic example could look like this:
someValue$ = source1$.pipe(
switchMap(val1 => useCond1 ? source2$ : of(val1))
);
someValue$.subscribe();
switchMap will subscribe to an "inner observable" whenever it receives an emission. The logic above says to either return the value emitted from source1$ (val1) or return whatever source2$ emits depending on the value of useCond1.
So source2$ will only get subscribed to when useCond1 is true;
Note: the function inside switchMap should return an observable (because switchMap subscribes to it), so of was used to turn the emitted value into an observable.
In your case, let's assume you want to emit some calculated value, based possibly on the other two sources.
We can use combineLatest to create a single observable based on 3 different sources. Since you only want to optionally call source2$ and source3$, we can define the sources based on your conditions. We can then use map to transform the array of values from the 3 sources, into the desired output:
someValue$ = source1$.pipe(
switchMap(val1 => {
const s1$ = of(val1);
const s2$ = useCond1 ? source2$ : of('default val2');
const s3$ = useCond2 ? source3$ : of('default val3');
return combineLatest([s1$, s2$, s3$]);
}),
map(([val1, val2, val3]) => {
return ... // your logic to return desired value
})
);
combineLatest will emit an array containing the latest emissions from each source whenever any source emits. This means someValue$ will emit the latest calculated value whenever any of the sources change.

rxjs - how can you create another observable from an observable but ignore its output in the parent observable?

I have a situation where I have an observable, and for each emitted item, I want to create another observable, but ignore that observable's value and instead return the result of the first observable.
For example, if I click a button, I want to track something that happens in another button, only when the first button is toggled on.
I can do this now, sort of, with a hack, by taking the output of the child observable and piping it to a mapTo with the parent's value. You can see it in this code, which can be played with in a code sandbox:
import { fromEvent, from } from "rxjs";
import { mapTo, switchMap, tap, scan } from "rxjs/operators";
const buttonA = document.getElementById("a");
const buttonB = document.getElementById("b");
const textA = document.querySelector('#texta');
const textB = document.querySelector('#textb');
fromEvent(buttonA, 'click').pipe(
// this toggles active or not.
scan((active) => !active, false),
switchMap(active => {
if (active) {
const buttonBClicks$ = fromEvent(buttonB, 'click');
// here we can observe button b clicks, when button a is toggled on.
return buttonBClicks$.pipe(
// count the sum of button b clicks since button a was toggled on.
scan((count) => count+1, 0),
tap(buttonBCount => {
textB.value = `button b count ${buttonBCount}`;
}),
// ignore the value of the button b count for the final observable output.
mapTo(active)
)
} else {
textB.value = ``;
return from([active]);
}
})
).subscribe({
next: buttonActive => {
textA.value = `Button a active: ${buttonActive}`
}
});
A couple issues here. In the case that the button is toggled on, the outer observable only receives a value once the button is clicked.
This mapTo use seems hacky.
Any better ways to do this?
It sounds like you don't want the inner observable to actually be a part of the process at all. Are you waiting on it or anything?
If not, you can just do it all as a side effect as follows:
fromEvent(buttonA, 'click').pipe(
scan((active) => !active, false),
tap(active => { if(active) {
fromEvent(buttonB, 'click').pipe(
scan(count => count+1, 0),
tap(buttonBCount => {
textB.value = `button b count ${buttonBCount}`;
})
).subscribe()
}})
).subscribe({
next: buttonActive => {
textA.value = `Button a active: ${buttonActive}`
}
});
Nested subscriptions are considered bad voodoo, so you ca refactor like this to keep your separation of conserns more apparent:
const trackActiveFromButton$ = fromEvent(buttonA, 'click').pipe(
scan((active) => !active, false),
shareReplay(1)
);
trackActiveFromButton$.subscribe({
next: buttonActive => {
textA.value = `Button a active: ${buttonActive}`
}
});
trackActiveFromButton$.pipe(
switchMap(active => active ?
fromEvent(buttonB, 'click').pipe(
scan(count => count+1, 0),
tap(buttonBCount => {
textB.value = `button b count ${buttonBCount}`;
})
) :
EMPTY
)
).subscribe();
Any better ways to do this?
The below may be better depending on your taste. It seems to me your sample code gets a little messy because you have a single observable that is trying to do too many things. And the side-effects are sort of mixed in with the stream behavior logic.
It's totally fine to be use tap() to do side-effect type things, but sometimes it can make it harder to follow. Especially in the above code, since there is a nested observable involved.
Creating separate observables that always emit specific data can make things easier to follow.
If we declare a stream to represent the isActive state and subscribe to that to update textA, and define a counter stream to represent the number of clicks that occurred while isActive = true, using that value to update textB, I think it makes it easier to follow what's going on:
const clicksA$ = fromEvent(buttonA, 'click');
const clicksB$ = fromEvent(buttonB, 'click');
const isActive$ = clicksA$.pipe(
scan(active => !active, false),
startWith(false)
);
const counterB$ = combineLatest([isActive$, clicksB$]).pipe(
scan((count, [isActive]) => isActive ? count + 1 : -1, 0)
);
counterB$.subscribe(
count => textB.value = count === -1 ? '' :`button b count ${count}`
);
isActive$.subscribe(
isActive => textA.value = `Button a active: ${isActive}`
);
To me, having the streams defined separately makes it easier to see the relationship between them, meaning, it's easier to tell when they will emit:
isActive derives from clicksA
counterB derives from clicksB & isActive
Here's a working StackBlitz
Also:
the outer observable only receives a value once the button is clicked
This can be solved using startWith() to emit a default value.

How to combine two list of object in one list with Rxjs

I have 2 differents list of objects and i want to combine them together for exemple:
listObj1 = [{name:'bob 1'}, {name:'bob 2'}]
listObj2 = [{pseudo:'Bob Razowski'}, {pseudo:'sponge bob'}]
result = [
{name:'bob 1', pseudo:'Bob Razowski}
{name:'bob 2', pseudo:'sponge bob'}
]
Can i do that with rxjs and how or if you have a better solution let me know
const characters = [];
const name$ = Observable.from(this.nameList)
.map(item => {
return {'name': item};
})
const pseudo$ = Observable.from(this.pseudoList)
.map(item => {
return {'pseudo': item};
})
Observable.zip(name$, pseudo$).subscribe(result => {
let char= {};
if(result.length > 1) {
char['name'] = result[0];
char['pseudo'] = result[1];
characters.push(char)
}
});
I started something like that but when i see the result, i can do it without rxjs. My question is more if it exists an other operator to do that.
thank
Well, you can do it with RxJS, but there is no obvious reason to do so, looking at your code snippet. One reason to do it reactively would be that you had really long lists and wanted to let the combination happen on (hypothetical) multiple threads. But in JavaScript that's not really practical, so for...of or Array.map are the right choices for this kind of task.
Anyhow, the RxJS solution would look like this:
zip(
from(listOb1),
from(listObj2)
).pipe(
map(([one, two]) => Object.assign({}, one, two)),
toArray()
)
Convert both lists into Observable streams with from, then zip them together and map each pair onto a new object using Object.assign. Collect the objects with toArray and done.

Observables and fetching paged data?

I need to create an observable, which I can "pull" data from, to work with a pageable api. I can only fetch 100 items per request, I want to be able to use observable as a generator function (on which I can call .next() to issue a request to get next 100 items.
I can't unfortunately find a way to do it with Rx. I suppose it's possible using controlled observable or a subject. Can you guys show me an example.
this is what I've gotten so far:
function list(entityType, viewName, fetchAll = false) {
var skip = 0,
total = 0;
const subject = new Rx.Subject(),
response$ = subject
.takeWhile(() => skip <= total)
.startWith(skip)
.flatMap((skip) => fetchPagePromise(skip)),
next = () => subject.onNext(skip);
if (fetchAll) {
Rx.Observable.timer(100, 100).subscribe(() => next());
}
return {
data$: response$.map(response => response),
next: fetchAll === true ? undefined : next
};
function fetchPagePromise() {
let limit = 100,
obj = {
viewName, limit, skip
},
qs = objectToQueryString(obj);
return $http.get(`${apiBase}/api/data/${entityType}${qs}`).then((res) => {
total = res.data.Total;
skip += limit;
return res.data.Rows;
});
}
}
this kinda works like a generator. it returns an Observable and next handler. Whenever next is called it pulls next 100 items from api and pushes into the Observable. Also if there’s a third parameter fetchAll passed, then it will keep fetching data until there’s no more. What scares me though that there are 2 mutating vars in function's closure - skip and total, and I don't know if managing them like this in asynchronous/unpredictable environment is ok.
One of the things you generally want to avoid is trying to make Rx into a plain old event emitter. Usually it is an indicator when you try and just trigger Observables manually by passing around a Subjects observer interface.
You should ask yourself, where is my data coming from? What calls next(), what calls that, etc. After enough of these you will generally find that this will lead you to something that can be wrapped by an Observable directly rather than explicitly calling next(). Also, I think the fetchAll flag should really be kept externally. You are only making the interface confusing by essentially turning it into a void method just by passing in a flag.
So I would recommend refactoring like so:
Rx.Observable.prototype.lazyRequest = function(entityType, viewName, limit = 100) {
var source = this;
return Rx.Observable.create(obs => {
var response = source
//Skip is really just the (limit * index)
.map((x, i) => i * limit)
.flatMap((skip) => {
let obj = {viewName, skip, limit},
qs = objectToQueryString(obj);
//Handle promises implicitly
return $http.get(`${apiBase}/api/data/${entityType}${qs}`);
},
//Return this with our skip information
(skip, res) => {skip, res})
//Publish it so the stream get shared.
.publish();
//This will emit once once you are out of data
var stop = response.first(x => x.skip >= x.res.data.Total);
return new CompositeDisposable(
//Complete this stream when stop emits
response.takeUntil(stop)
//Downstream only cares about the data rows
.map(x => x.res.data.Rows)
.subscribe(obs),
//Hook everything up
response.connect());
});
}
Then you can use it like so:
//An example of a "starting point", a button click
//Update the rows every time a new event comes through
Rx.Observable.fromEvent($button, 'click')
.startWith(0) //Inject some data into the pipeline
.lazyRequest(entityType, viewName)
.subscribe(/*Do something with the returned rows*/);
//Get all of the rows, will keep hitting the endpoint until it completes
Rx.Observable.interval(100)
.lazyRequest(entityType, viewName)
//Gather all the values into an array and emit that.
.toArray()
.subscribe();

Resources