RXJS Emit array item on event trigger - rxjs

Using RxJS Id like to emit each array item on an event/click.
I have below which works fine, but is there a cleaner way?
const testEventClick = new Subject();
const array = [1, 2, 3, 4, 5];
from(array)
.pipe(
concatMap(val => {
return new Observable(sub => {
testEventClick.subscribe(x => {
sub.next(val);
sub.complete();
});
});
})
)
.subscribe(console.log);
testEventClick.next();
testEventClick.next();

I would do the other way around and observe the subject.
Example: https://stackblitz.com/edit/rxjs-l4ttzh

Related

How to zip a list emitted by an observable into another observable?

Let's say I have one observable that emits at an interval of 1 sec. I have another observable that emits at an interval of 3 secs. The second observable emits a sequence of numbers. So every time that the second observable emits a sequence of numbers I want it to be zipped with the first observable.
For example:
observable 1
observable 2
zipped
1
[1]
2
[2]
3
[10,20,30,40]
[3, 10]
4
[4, 20]
5
[5, 30]
6
[10,20,30,40]
[6, 40, 10]
7
[7, 20]
If I am not wrong you are searching solution like this:
import { timer, combineLatest, Observable, BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
const first$ = timer(1000, 1000);
const second$ = new Observable<number[]>( subscriber => {
const intervalId = setInterval( () => {
subscriber.next([10,20,30,40]);
}, 3000)
return () => {
clearInterval(intervalId)
}
});
// This BehaviorSubject is used like
// some kind of "state" and keeps the emited
// array.
const state$ = new BehaviorSubject<number[]>([]);
second$.subscribe(state$);
combineLatest<[number,number[]]>([
first$,
state$
]).pipe(
map(([first, second]) => {
if (second.length > 0) {
return [first, second.shift()]
}
return [first]
})
).subscribe(
(value) => {
console.log(value);
}
);
Or at least your table describes it. Here is working stackblitz url:
https://stackblitz.com/edit/typescript-ufwqyh?file=index.ts

How to call "defaultIfEmpty" when list is empty on RxJS?

I've two lists with two distinct objects that need to be converted into the same type, the "second" list will be used only if the "first" list is empty, I tried to use the method defaultIfEmpty but it never return the second option.
const first = []; // could be [{code: 1}, {code: 2}]
const second = [{id: 1}, {id: 2}]
of(first).pipe(
map((value) => {number: value.code})
).pipe(
defaultIfEmpty(of(second).pipe(map((value) => {number: value.id})))
).subscribe(doSomething);
The desired output is:
[{number: 1}, {number: 2}]
On the example above, the map from defaultIfEmpty is never called;
how can I "switch" to another method source if the given source is empty?
will subscribe method be called after the map is complete, or it will be called for each item on map?
like this ?
const first = []; // could be [{code: 1}, {code: 2}]
const second = [{id: 1}, {id: 2}];
of(first).pipe(
filter(({length}) => length > 0),
defaultIfEmpty(second),
map((arr) => arr.map((x) => ({number: x.code ?? x.id})))
).subscribe(...);
If that's an option just create the right observable at runtime:
const makeObservable =
(arr1, arr2) =>
from(arr1.length ? arr1 : arr2)
.pipe(map(({code, id}) => ({number: code ?? id})));
const obs1$ = makeObservable([], [{id:1},{id:2}]);
const obs2$ = makeObservable([{code:2},{code:3}], []);
obs1$.subscribe(o => console.log(o));
obs2$.subscribe(o => console.log(o));
<script src="https://unpkg.com/rxjs#%5E7/dist/bundles/rxjs.umd.min.js"></script>
<script>
const {from} = rxjs;
const {map} = rxjs.operators;
</script>
const list1: { code: number }[] = [];
const list2 = [{ id: 1 }, { id: 2 }];
of(list1)
.pipe(
map((aList) => aList.map((v) => ({ 'number': v.code }))),
map((listModified) => {
return listModified?.length > 0
? listModified
: list2.map((value) => ({ number: value.id }));
})
)
.subscribe(console.log);
Don't get confused with map, one of them is from rxjs, the other is a function for arrays. In your first map, you are mapping the the whole array, not each element.
subscribe will be called when everything completes within the pipe.

How to concat two observable arrays into a single array?

example:
var s1 = Observable.of([1, 2, 3]);
var s2 = Observable.of([4, 5, 6]);
s1.merge(s2).subscribe(val => {
console.log(val);
})
I want to get
[1,2,3,4,5,6]
instead of
[1,2,3]
[4,5,6]
forkJoin works wells, you just need to flatten the array of arrays :
const { Observable } = Rx;
const s1$ = Observable.of([1, 2, 3]);
const s2$ = Observable.of([4, 5, 6]);
Observable
.forkJoin(s1$, s2$)
.map(([s1, s2]) => [...s1, ...s2])
.do(console.log)
.subscribe();
Output : [1, 2, 3, 4, 5, 6]
Plunkr to demo : https://plnkr.co/edit/zah5XgErUmFAlMZZEu0k?p=preview
My take is zip and map with Array.prototype.concat():
https://stackblitz.com/edit/rxjs-pkt9wv?embed=1&file=index.ts
import { zip, of } from 'rxjs';
import { map } from 'rxjs/operators';
const s1$ = of([1, 2, 3]);
const s2$ = of([4, 5, 6]);
const s3$ = of([7, 8, 9]);
...
zip(s1$, s2$, s3$, ...)
.pipe(
map(res => [].concat(...res)),
map(res => res.sort())
)
.subscribe(res => console.log(res));
Just instead of Observable.of use Observable.from that takes as argument an array and reemits all its values:
var s1 = Observable.from([1, 2, 3]);
var s2 = Observable.from([4, 5, 6]);
s1.merge(s2).subscribe(val => {
console.log(val);
});
Maybe instead of merge you might want to prefer concat but in this situation with plain arrays it'll give same results.
This will give you:
1
2
3
4
5
6
If you want this as a single array you could append also toArray() operator. Btw, you could achieve the same with Observable.of but you'd have to call it with Observable.of.call(...) which is probably unnecessary complicated and it's easier to use just Observable.from().
Maybe you could do this with List instead of Array:
var s1 = Rx.Observable.of(1, 2, 3);
var s2 = Rx.Observable.of(4, 5, 6);
and then
Rx.Observable.merge(s1,s2).toArray().map(arr=>arr.sort()).su‌​scribe(x=>console.l‌​og(x))
The accepted answer from #maxime1992 will now cause deprecation warnings with the current version of RXJS. Here's an updated version:
import { forkJoin, of } from 'rxjs';
import { map } from 'rxjs/operators';
const s1$ = of([1, 2, 3]);
const s2$ = of([4, 5, 6]);
Observable
.forkJoin([s1$, s2$])
.pipe(
.map(([s1, s2]) => [...s1, ...s2])
)
.do(console.log)
.subscribe();

RxJS unsubscribe with id

Upon unsubscribe, I need to call a function with an id that was originally sent in the data. What is the correct way of doing that?
It is a nested observable as seen in below snippet.
Observable.create(subscriber => {
const myData = nested.getData
.map(x => x > 1)
.subscribe(subscriber);
return () => {
myData.unsubscribe();
callWithIdThatIsInData({id:123})
};
})
When unsubscribed, I need to callWithIdThatIsInData() with a property what was sent in the nested observable..
Can anyone point me in the right direction please?
A solution is to store your properties which match your filter condition, during the subscription of the nested observable.
Here is an example :
const nested = Rx.Observable.from([1, 2, 3, 4, 5]);
const stream = Rx.Observable.create((subscriber) => {
const saved = [];
const myData = nested.filter(x => x > 1).subscribe(x => {
subscriber.next(x);
saved.push(x);
});
subscriber.complete();
return () => {
myData.unsubscribe();
//Here you have access to your data
console.log(saved);
}
});
stream.subscribe(x => console.log(x));

RxJS emit array items over time?

I'm trying to emit simple array values one after another with 500ms in between:
var a = Rx.Observable.from([1,2,3]);
a.interval(500).subscribe(function(b) { console.log(b); });
However, this throws an exception:
Uncaught TypeError: a.interval is not a function.
Three ways to do it, with RxJS version 6 :
1. Using concatMap
import { from, of, pipe } from 'rxjs';
import { concatMap, delay } from 'rxjs/operators';
const array = [1, 2, 3, 4, 5];
from(array)
.pipe(
concatMap(val => of(val).pipe(delay(1000))),
)
.subscribe(console.log);
2. Using zip and interval
import { from, pipe, interval } from 'rxjs';
import { delay, zip} from 'rxjs/operators';
const array = [1, 2, 3, 4, 5];
from(array)
.pipe(
zip(interval(1000), (a, b) => a),
)
.subscribe(console.log);
3. Using interval as source
import { interval, pipe } from 'rxjs';
import { map, take } from 'rxjs/operators';
const array = [1, 2, 3, 4, 5];
interval(1000)
.pipe(
take(array.length),
map(i => array[i])
)
.subscribe(console.log);
As already pointed out by xgrommx, interval is not an instance member of an observable but rather a static member of Rx.Observable.
Rx.Observable.fromArray([1,2,3]).zip(
Rx.Observable.interval(500), function(a, b) { return a; })
.subscribe(
function(x) { document.write(x + '<br \>'); },
null,
function() { document.write("complete"); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.2/rx.all.min.js"></script>
This is how I would do it:
var fruits = ['apple', 'orange', 'banana', 'apple'];
var observable = Rx.Observable.interval(1000).take(fruits.length).map(t => fruits[t]);
observable.subscribe(t => {
console.log(t);
document.body.appendChild(document.createTextNode(t + ', '));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.2/rx.all.min.js"></script>
var arrayList = [1,2,3,4,5];
var source = Rx.Observable
.interval(500/* ms */)
.timeInterval()
.take(arrayList.length);
source.subscribe(function(idx){
console.log(arrayList[idx]);
//or document.write or whatever needed
});
Pretty late but a simpler solution would be :
const arr = ["Hi,", "how", "may", "I", "help", "you?"];
Rx.Observable.interval(500)
.takeWhile(_ => _ < arr.length)
.map(_ => arr[_])
.subscribe(_ => console.log(_))
I find Weichhold technique to be the best but that it would gain in clarity of intent by extracting the zipped value outside of the zip:
// assume some input stream of values:
var inputs = Obs.of(1.2, 2.3, 3.4, 4.5, 5.6, 6.7, 7.8);
// emit each value from stream at a given interval:
var events = Obs.zip(inputs, Obs.interval(1000))
.map(val => val[0])
.forEach(console.log);
If you want to release samples over time, you can do something like this
const observable = interval(100).pipe(
scan((acc, value) => [value, ...acc], []),
sampleTime(10000),
map((acc) => acc[0])
);
I had a little different requirement, my array kept updating over time too. So basically I had to implement a queue which I can dequeue at a regular interval, but I didn't want to use an Interval.
If somebody has a need for something like this then probably this solution can help:
I have a function createQueue() that takes the array as an input and returns an Observable which we subscribe for listening to events from the Array at a regular interval.
The function also modifies the 'push()' method of the passes array so that whenever any item is pushed in the array, the Observable would emit.
createQueue(queue: string[]) {
return Observable.create((obs: Observer<void>) => {
const arrayPush = queue.push;
queue.push = (data: string) => {
const returnVal = arrayPush.call(queue, data);
obs.next();
return returnVal;
}
}).pipe(switchMap(() => {
return from([...queue])
.pipe(
concatMap(val => of(val)
.pipe(delay(1000)))
);
}), tap(_ => queue.shift()))
}
Lets say that the array is: taskQueue = [];
So, we need to pass it to the above function and subscribe to it.
createQueue(taskQueue).subscribe((data) => {
console.log('Data from queue => ', data);
});
Now, every time we do taskQueue.push('<something here>'), the subscription will trigger after a delay of "1000ms".
Please note: we should not be assigning a new array to the taskQueue after createQueue() has been called, or else we will loose the modified push().
Here is a dummy example for the above implementation: Test Example
Rx.Observable instance doesn't have interval method http://xgrommx.github.io/rx-book/content/core_objects/observable/observable_instance_methods/index.html. You can use like this.
Rx.Observable.interval(500)
.map(function(v) { return [1,2,3];})
.subscribe(console.log.bind(console));

Resources