Rxjs map only first emition - rxjs

Is there an operator that can let me map just the first emit ?
something like
import { from } from 'rxjs';
import { mapFirst } from 'rxjs/operators';
const source = from([1, 2, 3, 4, 5]);
const example = source.pipe(mapFirst(val => val + 10));
//output: 11,2, 3, 4, 5

If you want to write a user-land operator to do it:
import { OperatorFunction } from "rxjs";
import { map } from "rxjs/operators";
function mapFirst<T, R>(selector: (value: T) => R): OperatorFunction<T, T | R> {
return map<T, T | R>((value, index) => (index === 0) ? selector(value) : value);
}
And you'd use it as you have in your question.

Use first operator or take(1)
const source = from([1, 2, 3, 4, 5]);
const example = source.pipe(first());

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.

rxjs: extract slice of a collection given start and end matching rules

I'm trying to write a reactive function with rxjs that, given a potentially infinite array:
Rule 1: Skip initial null items
Rule 2: Extract the items between two '*' appearances
Rule 3: If first item after nulls is not an '*', must fail (or return an empty array)
Rule 4: Process no more than N items
Rule 5: If there's no a second '*', must fail (or return an empty array)
So, with N = 10:
Case 1: [null, null, '*', 1, 2, 3, '*', 4, 5] -> [1, 2, 3]
Case 2: [null, null, 1, '*', 2, 3, '*', 4, 5] -> [] // Breaks rule 3
Case 3: [null, null, '*', 1, 2, 3, 4, 5, 6, 7, 8, '*'] -> [] // Breaks rule 5 (second * is at position > N)
For the case 1, there's no problem. But I don't find the set of operator to enforce the rules 3 and 5
This example illustrates the problem:
const { from } = require('rxjs');
const { take, takeWhile, skipWhile, toArray } = require('rxjs/operators');
function *infinite(items) {
for (let i = 0; ; i++) {
yield i < items.length ? items[i] : `fake${i}`
}
}
const extract = ({
source,
limit = 10,
}) => new Promise(resolve => {
source
.pipe(...[
take(limit),
skipWhile(item => item === null),
skipWhile(item => item === '*'),
takeWhile(item => item !== '*'),
toArray(),
])
.subscribe(result => {
resolve(result)
})
})
;(async () => {
console.log(await extract({ source: from(infinite([null, '*', 1, 2, 3, '*', 4, 5, 6])) }))
console.log(await extract({ source: from(infinite([null, 'a', '*', 1, 2, 3, '*', 4, 5, 6])) }))
console.log(await extract({ source: from(infinite([null, '*', 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12])) }))
})()
Edit: I realized the operation:
skipWhile(item => item === '*'),
is not accurate. Should be something like
skipThisSingleItemIfMatchAsteriskOtherwiseFail
A possible solution for your problem is the following. Comments are inline
function extract(c: any[], n: number) {
// first you create an src stream where all the leading nulls are removed
const src = from(c).pipe(
// just take the first n elements
take(n),
// filter to remove the nulls - this can be a problem if you have nulls between the 2 '*'
filter((item) => item !== null),
// share is used to avoid having more than one subscription to this stream
share()
);
const core = src.pipe(
// here we consider all elements until the second '*' is met
// And what about the first '*'? see the rest of the code, there is the explanation
takeWhile((item) => item !== "*", true),
// create an array which cumulates all the values received until the stream completes
toArray(),
// if the array of all elements received is empty or if the last element is not a '*' return []
// else return the elements received a part the last '*'
map((arr) => {
return arr.length === 0
? []
: arr[arr.length - 1] !== "*"
? []
: arr.slice(0, arr.length - 1);
})
);
// this is the stream returned by the extract function
// it starts from the src stream we have created above
return src.pipe(
// the first element is taken
// since the src stream is shared between this stream and the stream we have called "core" and have built above
// then it means that the first element is consumed here and will not be encountered in the "core" stream
first(),
// if the first element is not a '*' an error is thrown
tap((d) => {
if (d !== "*") {
throw new Error("First not null val is not *");
}
}),
// if no error is thrown then we return the stream "core"
concatMap((firstItem) => {
return core;
}),
// if an error is thrown then we return an Observable which emits []
catchError((e) => of([]))
);
}
In order to use this function you can write the following code
const resp = extract(source, 10);
resp.subscribe((d) => {
// do stuff with the result, for instance
console.log(d);
});
Here a stackblitz that reproduces this logic
How about something with custom RxJS operators? Try the following
const { Observable, from } = rxjs;
const { take, filter, reduce } = rxjs.operators;
const onlyIfFirst = predicate => {
let first = true;
return source =>
new Observable(subscriber =>
source.subscribe({
next(value) {
if (first) {
first = false;
if (predicate(value)) {
subscriber.next(value);
} else {
subscriber.next([]);
subscriber.complete();
}
} else {
subscriber.next(value);
}
},
complete() {
subscriber.complete();
}
})
);
};
const toArrayWhen = (predicate, count) => {
let id = 0;
let times = count * 2;
return source =>
source.pipe(
reduce((acc, curr) => {
if (!!id) {
if (predicate(curr)) id++;
if (id < times && !predicate(curr)) {
acc = [...acc, curr];
}
} else {
if (predicate(curr)) id++;
}
return acc;
}, [])
);
};
const input = [null, null, '*', 1, 2, 3, '*', 3, '*', 4, '*'];
from(input)
.pipe(
take(10),
filter(value => value !== null),
onlyIfFirst(value => value === '*'),
toArrayWhen(value => value === '*', 1)
)
.subscribe({
next: value => console.log('Next:', value),
error: error => console.log('Error:', error),
complete: () => console.log('Complete')
});
<script src="https://unpkg.com/rxjs#6.2.2/bundles/rxjs.umd.min.js"></script>
Note: I'm fairly certain the count variable behavior for > 1 is buggy at the moment. But as long as you only need the first instead of values between two asterisks *, it should be find.

RxJS: sequentially concat a variable number of inner Observables

I need to sequentially concat a variable number of inner Observables and stop as soon as the first one resolved to a given result.
See example below or here on Stackblitz where I use concatMap, but this works with a fixed number of inner Observables only.
const values = [1, 4, 6, 3, 9];
getRemoteValue(values[0]).pipe(
concatMap(result => result ? of(result) : getRemoteValue(values[1])),
concatMap(result => result ? of(result) : getRemoteValue(values[2])),
concatMap(result => result ? of(result) : getRemoteValue(values[3])),
concatMap(result => result ? of(result) : getRemoteValue(values[4]))
).subscribe(success => console.log(success ? 'found it' : 'failed'));
function getRemoteValue(input: number): Observable<boolean> {
console.log(`checking ${input}`)
// this would be an async remote call in real life
const value = _.random(10);
return of(value === input);
}
import * as _ from 'lodash';
import { Observable, of, from, EMPTY } from 'rxjs';
import { concatMap, single, find } from 'rxjs/operators';
const values = [1, 4, 6, 3, 9];
function getRemoteValue(input: number): Observable<boolean> {
console.log(`checking ${input}`)
// this would be an async remote call in real life
const value = _.random(10);
return of(value === input);
}
from(values).pipe(
concatMap(getRemoteValue),
find((v) => typeof v === 'boolean' && v === true)
)
.subscribe(success => {
console.log('success:', success);
console.log(success ? 'found it' : 'failed');
}
);
stackblitz

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