I tried to de-duplicate the sent arrays and then merge them into arrays
import { from, BehaviorSubject, distinct, switchMap, toArray } from "rxjs";
let list$ = new BehaviorSubject([1, 2, 3, 2, 3, 5]);
list$.pipe(
switchMap((e) => from(e)),
distinct(),
toArray()
).subscribe(console.log);
expected result:
BehaviorSubject -> [1, 2, 3, 2, 3, 5]
switchMap -> 1 2 3 2 3 5
distinct -> 1 2 3 5
toArray -> [1, 2, 3, 5]
console.log -> [1, 2, 3, 5]
Actually did not receive any value in console.log, why is this and how can I work as expected
"rxjs": "^7.2.0"
toArray only emits once the source observable completes.
The following should work as expected.
list$.pipe(
take(1),
switchMap(e => e),
distinct(),
toArray()
).subscribe(console.log);
If what you really want to do is filter unique values of an array, then RxJS's unique operator might be overkill. I wouldn't bother turning your array into a stream. Just filter the array.
list$.pipe(
map(a => [...new Set(a)])
).subscribe(console.log);
So, if the source does not stop after the first notification, I assume that it will continue emit other arrays and that you want to filter the duplicates on each array emitted. In other words, if the list$ of your example emits first [1, 2, 3, 2, 3, 5] and then [3, 2, 1, 6, 6, 6,] what you want to log are 2 arrays, [1, 2, 3, 5] and [3, 2, 1, 6].
If my assumption is right, than the solution could be the following
list$.pipe(
concatMap((e) => from(e).pipe(
distinct(),
toArray()
)),
).subscribe(console.log);
The trick here is that each from(e) stream will complete when there are no more elements in the array. Therefore, since it completes, the toArray operator can actually work.
scan could do the trick.
list$.pipe(
switchMap((e) => from(e)),
distinct(),
scan((acc, curr) => [...acc, curr], []),
).subscribe(console.log);
// will print: [1], [1, 2], [1, 2, 3], [1, 2, 3, 5]
You could insert debounceTime in the pipe, if you need less emissions:
list$.pipe(
switchMap((e) => from(e)),
distinct(),
scan((acc, curr) => [...acc, curr], []),
debounceTime(0)
).subscribe(console.log); // will print [1, 2, 3, 5]
If the only requirement is to remove duplicates, you're better off handling it using vaniall JS. See here: https://stackoverflow.com/a/9229821/6513921
We'll take the shortest solution without any regards to performance: uniq = [...new Set(array)];
You could then write a custom RxJS operator to include it in the pipe with other operators.
const { BehaviorSubject, from } = rxjs;
const { map, switchMap } = rxjs.operators;
const uniqueArray = (obs$) => {
return (obs$) => {
return obs$.pipe(
map(arr => [...new Set(arr)])
);
};
};
const sub = new BehaviorSubject([1, 2, 3, 2, 3, 5]);
sub.asObservable().pipe(
uniqueArray()
).subscribe(console.log);
sub.next([6, 3, 1, 6, 7, 1, 1]);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs#6.2.2/bundles/rxjs.umd.min.js"></script>
Related
I have an observable of numbers [1, 3, 5, 2, 4, 1, 3] and want to window it by groups of same remainder of the division by two. So, resulting observable of observables will be [[1, 3, 5], [2, 4], [1, 3]]. How can I do it?
The below snippet can produce the results you want, although I'm not sure what you are trying to achieve with this.
The reduce will add numbers to the last subarray, until one of them does not have the same result with the others when divided by two.
The map converts the array of subarrays to array of observables
from([1, 3, 5, 2, 4, 1, 3])
.pipe(
reduce((result, num) => {
const lastArray = result[result.length - 1];
if (lastArray.length > 0 && lastArray[0] % 2 == num % 2) {
lastArray.push(num);
result[result.length - 1] = lastArray;
} else {
result.push([num]);
}
return result;
}, [[]]),
map((array: any[]) => array.map((subarray) => from(subarray)))
)
.subscribe((res) => {
res.forEach((obs, i) =>
obs.subscribe((data) => console.log(data, 'observable ' + i))
);
});
working example
I have the following code using RxJs Zip operator:
of(1, 2, 3)
.pipe(zip(of(5), of(10)))
.subscribe((a) => console.log(a));
The output will be
[1, 5, 10]
But what happened to the 2 and 3 values initialized at of(1, 2, 3)?
The inner observables of(5) and of(10) only have one element. The static method zip() will only emit when all observable emit a corresponding notification.
For eg.
of(1, 2, 3)
.pipe(zip(of(5, 6, 7), of(10, 11, 12)))
.subscribe((a) => console.log(a));
will emit
[1, 5, 10]
[2, 6, 11]
[3, 7, 12]
I must also stress here that the static method zip() is deprecated and has since been replaced by the zip() function. Using the new zip() the equivalent for the above would be
import { of, zip } from 'rxjs';
zip(
of(1, 2, 3),
of(5, 6, 7),
of(10, 11, 12)
).subscribe((a) => console.log(a));
Suppose I have an array of integers and I want to iterate over this array in a interval of one second, the current second being the index of the array. Once the index reaches the end, I want to reset the interval value. Whats the right approach to achieve this behavior?
Stackblitz example
Code:
const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
interval(1000)
.pipe(
tap(second => {
if (array[second]) {
console.log(array[second]);
} else {
//reset interval to run in a cyclic manner?
}
})
).subscribe();
So you want to repeat the same sequence indefinatelly.
import { from, of } from "rxjs";
import { concatMap, repeat, delay } from "rxjs/operators";
const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
from(array)
.pipe(
concatMap(second => of(second).pipe(
delay(second * 1000),
)),
repeat(),
)
.subscribe(console.log);
Live demo: https://stackblitz.com/edit/rxjs-interval-pmlj6e
I'm using RxJS v6, but the answer could apply to RxJS v5 as well.
I'd like to make it so if I have say 8 values, I would only have 4 actively being processed at a time.
The code I have right now sends in 4 items, then waits till all 4 finish, then sends in the next 4. I'd like it to send in the first 4, then as an observable completes, another one comes in to be processed.
from([1, 2, 3, 4, 5, 6, 7, 8])
.pipe(
bufferCount(4),
concatMap(values => (
from(values)
.pipe(
mergeMap(value => (
timer(1000) // Async stuff would happen here
.pipe(
mapTo(value),
)
)),
)
)),
)
.subscribe(console.log)
You have to use the second parameter of mergeMap, which is a number that allows to specify the concurrency level you want.
So your code could look like
from([1, 2, 3, 4, 5, 6, 7, 8])
.pipe(
map(val => of(val)),
mergeMap(val => val, 4),
)
.subscribe(console.log)
or
of([1, 2, 3, 4, 5, 6, 7, 8])
.pipe(
mergeMap(val => val, 4),
)
.subscribe(console.log)
Consider that concatMap is mergeMap with concurrency level set to 1.
Read this SO post for more details on mergeMap.
I'm weirdly stuck with the following:
I have an Observable that emits either an array with some items or an empty array, and this Observable emits only once - let's call it startingArray$
Then I have a hot Observable that emits individual items that I would like to push to startingArray once startingArray has been emitted - let's call it additions$
What code gives me the resulting observable startingArrayPlusAdditions$ that continuosly grows as more items are emitted?
startingArrayPlusAdditions$ should emit every time additions$ emits, but it should not emit when startingArray$ emits initially
Not sure that I understood all your problem but here's a proposal:
const { Observable } = Rx;
const startingArray$ = Observable.of([1, 2, 3]);
const additions$ = Observable.from([4, 5, 6, 7, 8]);
const startingArrayPlusAdditions$ = startingArray$
.combineLatest(additions$)
.scan((acc, current) => {
const [startingArray, addition] = current;
if (acc === null) {
return [...startingArray, addition];
} else {
acc.push(addition);
return acc;
}
}, null)
.do(console.log)
.subscribe();
The output is:
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7, 8]
So just as you asked:
startingArrayPlusAdditions$ should emit every time additions$ emits, but it should not emit when startingArray$ emits initially
Here's a working Plunkr: https://plnkr.co/edit/rKXLJrmA7mSzpQgoemlD?p=preview