Why rxjs print data one single character every time? - javascript

This is my code in RxJs6:
const observable$ = interval(1000).pipe(
take(2),
map(x => interval(1500).pipe(
map(y => x+':'+y),
take(2),
concatAll()
)),
);
observable$.subscribe(obs => {
obs.subscribe(x => console.log(x));
});
I expect my code show the result like this:
0:0
1:0
0:1
1:1
But it actually shows:
why my code print data only one character every time ? And I think it should work like what i expected above not the actual result. anything wrong i understand about rxjs ?

This is because of concatAll(). It's typically used to flatten nested Observables but it can work with Promises and arrays (array-like objects) as well. Ant this is exactly what you're seeing here.
It thinks you want to flatten an array even when you have a string so it takes each item in the array (character in your case) and reemits it separately.
However, another question is what you wanted to achieve with concatAll.

Related

How to properly flat this Observable array?

I need to flat the results of a Observable<Order[]>[] in a Order[].
The current way that I'm doing it:
const ordersObservable: Observable<Order[]>[] = [];
//ordersObservable is populated with a bunch of Observable<Order[]>
forkJoin(ordersObservable)
.pipe(
map((results) => ([] as Order[]).concat(...results))
)
.subscribe((orders: Order[]) => {
this.orderService.set(orders);
//...
});
I've been told that I shouldn't be using pipe like that and should be using a RxJs function to handle that.
I've tried to use concatAll and mergeAll instead of the map, but that ended in calling the orderService for every item in ordersObservable, instead a single time with a flat array with all results of ordersObservable.
What am I doing wrong? And how can I flat the results of all the observables in a single array, preferably with a native RxJs solution?
An Observable is meant to emit values. With an array of Observables, we need to subscribe to each element of the Observable array to emit the Order array.
I honestly think you should re-evaluate creating an array of Observables. If you want to post another question with the code that is generating that array of Observables, we could provide suggestions.
That said, I was able to get something to work. I didn't spend the time to see if there was an easier approach.
from(this.ordersObservable)
.pipe(
concatAll(),
scan((acc, value) => [...acc, ...value], [] as Order[]),
takeLast(1)
)
.subscribe((x) => console.log('result', JSON.stringify(x)));
First, I needed something to subscribe to. We can't subscribe to the array of Observables. So I used from to turn the Array of Observables into another Observable. That way I could subscribe and get the code to execute.
The from emits each Observable from the array.
NOTE: forkJoin and combineLatest also work instead of from and provide the same result.
I then use concatAll() to concatenate the inner Observables in sequence. It subscribes to each Observable in the array.
scan allows us to define an accumulator, accumulating each array of Orders into a single array of orders.
UPDATE: Added takeLast(1) to ensure only the last result is emitted.
UPDATE 2: Second option
This also worked and may be a bit simpler:
concat(...this.ordersObservable)
.pipe(
scan((acc, value) => [...acc, ...value], [] as Order[]),
takeLast(1)
)
.subscribe((x) => console.log('result', JSON.stringify(x)));
This uses array destructuring (the "...") with the concat operator to emit all of the values from all of the Observables in the array of Observables. Then we still use scan to accumulate them and takeLast(1) to emit only once.
I would be interested to know if anyone else is able to simplify this!

RxJS operator for left joining 2 observables

I want to left join 2 Observables such that I always take the value from ObservableA, unless there is a corresponding value from ObservableB. In which case I want to use the value from ObservableB. The two Observables should join so will be a akin to a SQL left join.
From reading the RxJS Operators tree it does seem to me that Join would be the logical choice. I am unclear on how the join window works and how to ensure either observable could result in a join happening.
Can anyone provide a simple example?
You can do this easily with just merge where you put ObservableB first and then use take(1) to complete immediately. If ObservableB was empty then it'll emit the value from ObservableA.
const a$ = of(1);
const b$ = of('a');
merge(b$, a$)
.pipe(
take(1),
)
.subscribe(console.log);
Live demo: https://stackblitz.com/edit/rxjs6-demo-u5syjx?file=index.ts
There seems to be no out of the box operator for this. But you may want to zip first, then map the results with your custom logic to get what you want:
zip(observableA, observableB).map([a,b] =>{
if(idMatchesWithB) {return b}
else {return a}
}

Array of numbers gets converted to an array of key-value pairs;

I created this function in an Angular4 app:
enrollmentCheck() {
this.allCourses.forEach(course => {
this._courses.getCurrentEnrolment(course.slug).subscribe(res => {
if(res.length > 0){
this.enrolledCourses.push(res[0].course_id);
console.log(this.enrolledCourses);
}
})
});
console.log(this.enrolledCourses);
}
It is supposed to iterate through an array of objects and check if the user is enrolled to any of them.
The first bit works well, the subscribtion gives me the right data (res). I then need to store the property course_id into an array.
The first log (inside the loop), seems to work fine. I get
[1]
[1,2]
[1,2,5]
[1,2,5,7]
as outputs, one for each time the loop is executed.
Problem is that the second log (outside the loop), will output something like:
[
0: 1
1: 2
2: 5
3: 7
]
rather than
[1,2,5,7]
as I would like, for I will need to iterate through this array, and I cannot find a way to do it with the one I get.
Can anyone help? I apologise if this may seem a silly question to someone, but any help would be really appreciated.
Thanks,
M.
There are a few problems with your method. First of all you're creating subscriptions inside a loop, that's a bad idea because you're never completing them. Second you're doing asyc operations inside the loop therefore at the time the second console log appears the data might not be there yet.
A better solution would be to use Observable.forkJoin to wait for all async requests and then map the data.
For example
enrollmentCheck() {
Observable.forkJoin(
this.allCourses.map(course => {
return this._courses.getCurrentEnrollment(course.slug);
}
).map(res => {
return res
.filter(enrollment => enrollment.length > 0)
.map(enrollment => enrollment[0].course_id)
}).subscribe(data => console.log(data))
}

RxJS 1 array item into sequence of single items - operator

Given such observable
Rx.Observable.of([1,2,3,4,5])
which emits a single item (that is an array), what is the operator that will transform this observable to a one that emits 5 single items (or whatever the array consists of)?
The example is on .of, but it would be the same for fetching arrays via promises, there might be many other examples. Don't suggest to replace of with from
I can't think of an existing operator to do that, but you can make one up :
arrayEmitting$.concatMap(arrayValues => Rx.Observable.merge(arrayValues.map(Rx.Observable.of)))
or the simpler
arrayEmitting$.concatMap(Rx.Observable.of)
or the shortest
arrayEmitting$.concatMap(x => x)
That is untested so let me know if that worked for you, and that uses Rxjs v4 API (specially the last one). This basically :
process each incoming array of values as one unit (meaning that the next incoming array will not interlap with the previous one - that is why I use concatMap)
the incoming array is transformed into an array of observables, which are merged : this ensures the emission of values separately and in sequence
You can also use the flatMap operator (https://stackoverflow.com/a/32241743/3338239):
Observable.of([1, 2, 3, 4])
.flatMap(x => x)
.subscribe(x => console.log(x));
// output:
// 1
// 2
// 3
// 4
You can use from now to convert an array into a sequence.
https://www.learnrxjs.io/operators/creation/from.html
from([4,5,6])
mergeAll:
Observable.of([4, 5, 6])
.mergeAll()
.subscribe(console.log);
// output:
// 4
// 5
// 6
Dont understand why concatMap(x => x) or flatMap(x => x) works, it doesnt change anything.
This should work (rxjs 6 or above):
of([4,5,6]).pipe(mergeMap(from))

RXJS How to convert Observable<T[]> to Observable<T>

I would like to take an Observable<T[]> and convert it to an Observable<T> such that each array from the Observable<T[]> is broken up and the individual elements of the arrays are then emitted, separately, via the Observable<T>.
Is there a standard operator for doing this? I've searched around but haven't found anything. Thanks.
After being pointed in the direction of concatMap/flatMap, I came up with the following general solution:
var source: Observable<T[]>;
...
var splitSource = source.flatMap<T>((x:T[]) => { return Rx.Observable.fromArray(x); });
You could use concatMap like this:
function identity (x) {return x}
var obs_of_array$ = Rx.Observable.return(['1','2','you got it'])
.concatMap(identity)
This works because concatMap also accepts arrays (and observables and promises) as the return value of the function selector that you pass as a parameter. Jsbin here, documentation here
So the array passed as parameter becomes the return value from the selector function, and then is flattened by the concatMap operator while respecting the ordering of the array.
I've also been playing around with converting Observable<T[]> to Observable<T>.
I've found that flatMap() does the job on it's own, which I presume was not the case 11 months ago when the question was asked.
To illustrate, in the jsfiddle in #user3743222's answer, you can make this change and get the same output:
//.concatMap(identity)
.flatMap(theArray=>theArray)
I use this and the toArray() operator to perform set operations in the pipeline, for example to sort the observable.
let sorted$ = source$.toArray().flatMap(theList=> theList.sort(this.myComparer) )
The output is Observable<T>. (N.B works in the context of a fixed array, not sure what would happen with a continuously generating source)

Categories

Resources