RxJS emit array items over time? - rxjs

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));

Related

Window before nested emits

I have observable [1,1,1,2,2,1,1], and want to convert it in observable of sums of same elements group, so result would be [3,4,2]. I wrote this code:
from([1, 1, 1, 2, 2, 1, 1])
.pipe(
connect((numbers$) =>
numbers$.pipe(window(checkChange(numbers$)), mergeMap(sumNumbers))
)
)
.subscribe(console.log);
function checkChange(obs$: Observable<number>): Observable<any> {
return obs$.pipe(
pairwise(),
filter(([a, b]) => a !== b),
map(() => "change")
);
}
function sumNumbers(obs$: Observable<number>): Observable<number> {
return obs$.pipe(reduce((acc, n) => acc + n, 0));
}
But it returns observable [5,3,1]. This is because original observable windows after element where change happens, and not before. So it groups by [[1,1,1,2],[2,1],[1]], and not [[1,1,1],[2,2],[1,1]]. How can I fix it?
You could achieve it using other notifier observable that runs before the window observable gets processed.
You could do something like this
const source = from([1, 1, 1, 2, 2, 1, 1]);
const myNotifier = new Subject<number>(); //Subject to be used as notifier
source
.pipe(
connect((shared$) =>
merge(
shared$.pipe(notifyOnChange(myNotifier)), // 1st run the notifier stream
shared$.pipe(window(myNotifier)) // 2nd the windowed one.
)
),
mergeMap(sumNumbers)
)
.subscribe(console.log);
function notifyOnChange<T>(notifier: Subject<T>) {
return (obs$: Observable<T>): Observable<never> =>
obs$.pipe(
distinctUntilChanged(), // only lets through the value if different to previous one
skip(1), // skip the first emission
tap(notifier), // notify the change
ignoreElements() //prevent the next notification propagation
);
}
function sumNumbers(obs$: Observable<number>): Observable<number> {
return obs$.pipe(reduce((acc, n) => acc + n, 0));
}
Cheers
I would use reduce instead to maintain the current value being counted, running count of correct value, and an array of previous counts.
import {from} from 'rxjs';
import {reduce,map} from 'rxjsoperators';
from([1, 1, 1, 2, 2, 1, 1]).pipe(
reduce(
({ prevCounts, prev, count }, cur) => (count === undefined) ? {
prevCounts,
prev : cur,
count : 1
} : (prev === cur) ? {
prevCounts,
prev,
count: count + 1
} : {
prevCounts: [...prevCounts, count],
prev: cur,
count: 1
},
{ prevCounts : [] }
),
// append count to prevCounts array
map(({ prevCounts, count }) => (count) ? [...prevCounts, count] : prevCounts)
).subscribe({
next: x => console.log(x)
});
This code doesn't use array.push because that changes the array in place and while in this case it wouldn't be a bad thing I've learned it's safer to have the functional programming habit of either returning unchanged objects or new objects.

Why is a Promise changing behavior of getValue in RXJS?

I had an issue where adding an extra pipe to subscription to a BehaviorSubject was making the wrong behavior in some tests. Whenever I did const stores = await lastValueFrom(workingStore$); in RXJS 7 or const stores = await workingStore$.toPromise(); in RXJS 6, the value was not what I expected. I reduced the code down to this fiddle: https://jsfiddle.net/Dave_Stein/v7aj6bwy/
You can see on the run without a concatMap, getValue gives 3 values in an array. With concatMap, it will only return the first value.
The same can be observed when I use toPromise in this way:
console.log('here i am', bug);
const promise = workingStore$.toPromise();
from(events).subscribe(events$);
const x = await promise;
console.log('there i am', bug, x);
I get that there is an async behavior going on with concatMap, but I would imagine using toPromise would make RXJS wait for all the events being processed via subscribe to complete before resolving the promise.
In reality my concatMap calls a method that is async and MUST use await based on a library I am using.
Is there some other way to accomplish this? Order of events matters to me which is why I chose concatMap
The solution is at: https://jsfiddle.net/Dave_Stein/nt6Lvc07/.
Rather than trying to subscribe to workingStore$ twice, I can use mergeWith operator in RXJS 7. (There is another way to accomplish this in 6). Using subscribe on the same subject twice is a bad practice that can lead to issues like these apparently.
const { Subject, operators, pipe, BehaviorSubject, from, lastValueFrom } = rxjs;
const { filter, scan, concatMap, mergeWith } = operators;
const run = async (bug) => {
const events$ = new Subject();
const historyChanges$ = new Subject();
const workingStore$ = new BehaviorSubject({});
const scanWorkingStoreMap = {};
events$.pipe(
concatMap((evt => {
return Promise.resolve(evt);
})),
filter((evt) => evt.name === 'history')
).subscribe(historyChanges$);
const newDocs$ = events$
.pipe(
filter((evt) => evt.name === 'new'));
// historyChanges$ is a Subject
historyChanges$
.pipe(
mergeWith(newDocs$),
scan((acc, evt) => {
const { id } = evt;
if (!acc[id]) {
acc[id] = [evt]
} else {
acc[id].push(evt)
}
return acc;
}, scanWorkingStoreMap),
)
.subscribe(workingStore$);
const events = [
{ name: 'new', id: 1},
{ name: 'history', id: 1, data: { a: 1}},
{ name: 'history', id: 1, data: { a: 2}}
]
console.log('here i am', bug);
from(events).subscribe(events$);
console.log('there i am', await lastValueFrom(workingStore$));
}
run();

RXJS Emit array item on event trigger

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

RXJS: Subscription to share operator causes strange behaviour

Using these RxJS tools: BehaviorSubject, Subscribe and Next
Please refer to the this codesandbox, and look at the console to see a visual: https://codesandbox.io/s/fancy-bird-0m81p
You will notice the object "C" value in the subscription is "1 stream behind
Consider the the following code:
const initialState = { a: 1, b: 2, c: 3 };
const Store$ = new BehaviorSubject(initialState);
const StoreUpdates$ = Store$.pipe(
scan((acc, curr) => {
return Object.assign({}, acc, curr);
}, initialState),
share()
);
export const updateStore = update => {
Store$.next(update);
};
StoreUpdates$.pipe(
distinctUntilChanged((p, n) => {
return p.b === n.b;
})
).subscribe(store => {
Store$.next({ c: Math.random() });
});
StoreUpdates$.subscribe(store => {
console.log("Subscription Check:: Notice issue here", store);
});
When you call the updateStore function, in the console.log you will notice that the C value, which is updated in a next call within a subscription, appears in the first iteration and the older value appears in the last iteration. So somehow it looks the next.call within the subscription happens "Before"
I believe the codesandox will illustrate and make it more clear.
How do I maintain the correct order of events so that the latest update appears last in the subscription?
For the moment a non-ideal way of solving this issue is to wrap the next statement within the subscribe in a timeout. If anyone has a better solution please do post.
StoreUpdates$.pipe(
distinctUntilChanged((p, n) => {
return p.b === n.b;
})
).subscribe(store => {
setTimeout(() => {
Store$.next({ c: Math.random() });
},0)
});

Is there a build in Observable that fires next after the previous in the chain has completed?

I am looking for some pre-built functionality that does something like this:
const Rx = require('rxjs');
const proto = Rx.Observable.prototype;
proto.whenCompleted = function () {
const source = this;
return Rx.Observable.create(sub => {
const ret = [];
return source.subscribe(
function (v) {
ret.push(v);
},
function (e) {
sub.error(e);
},
function () {
sub.next(ret);
}
)
});
};
is there an RxJS observable method that can do that?
concat allows you to merge streams "one after another".
E.g.: in the case below, the stream will first emit 1, 2, 3 and only after that will send the form changes.
from([1, 2, 3])
.pipe(
concat(
myForm.get('age').valueChanges
)
)

Resources