Related
Question about rxjs puzzle.
I have the input observable stream and it will emit after 3 secs when I type some.
import { fromEvent, interval } from "rxjs";
import { debounce } from "rxjs/operators";
// input is HTMLInputElement
const input$ = fromEvent(input, "input");
input$
.pipe(debounce(() => interval(3000)))
.subscribe(e => console.log(e.target.value));
I would like to make a change to cancel the debounce and emit immediately once the button is clicked. But, if I don't click the button, it will wait 3 secs.
import { fromEvent, interval } from "rxjs";
import { debounce } from "rxjs/operators";
const input$ = fromEvent(input, "input");
// add click observable stream
const click$ = fromEvent(button, "click");
input$
.pipe(debounce(() => interval(3000)))
// I can't get this to work in the mix!!
// .pipe(debounce(() => click$))
.subscribe(e => console.log(e.target.value));
How can this be achieved?
sounds like a race operator.
const input$ = fromEvent(input, "input");
const click$ = fromEvent(button, "click");
input$
.pipe(
switchMap(value => race(
click$,
timer(3000),
).pipe(
take(1),
mapTo(value),
)),
.subscribe(e => console.log(e.target.value));
Here is the solution to toggle debounce, what you have to do is to convert interval() to a stream that change interval time base on button click
Js
import { fromEvent, interval,timer} from 'rxjs';
import { debounce,scan,shareReplay,map,startWith,tap,switchMap} from 'rxjs/operators';
const input = fromEvent(document.getElementById('text'), 'input');
const debounceToggle=fromEvent(document.getElementById('toggle'),'click').pipe(
scan((acc,curr)=>!acc,false),
map(on=>on?0:3000),
startWith(3000),
shareReplay(1),
switchMap(value=>interval(value))
)
const result = input.pipe(debounce(() => {
return debounceToggle
}));
result.subscribe(x => console.log(x.target.value));
HTML
<button id="toggle">toggle debounce</button>
<input type="text" id="text"/>
Here could be another solution I think:
input$
.pipe(
debounce(
() => interval(3000).pipe(takeUntil(buttonClick$))
)
)
.subscribe(e => console.log(e.target.value));
debounce will emit the value that caused the inner observable's subscription, when it either completes/emits a value
// Called when the inner observable emits a value
// The inner obs. will complete after this as well
notifyNext(outerValue: T, innerValue: R,
outerIndex: number, innerIndex: number,
innerSub: InnerSubscriber<T, R>): void {
this.emitValue();
}
// Called when the inner observable completes
notifyComplete(): void {
this.emitValue();
}
Source code
The following would be the simplest in my opinion:
const input$ = fromEvent(input, "input");
const click$ = fromEvent(button, "click");
merge(
input$.pipe(debounceTime(3000)),
click$
).pipe(
map(() => input.value)
).subscribe(val => console.log(val));
https://stackblitz.com/edit/rxjs-8bnhxd
Also, you are essentially "combining" 2 different events here, it doesn't make sense to me to rely on event.target.value, as it could be referring to different things which makes it hard to read.
I have three observables foo$, bar$ and baz$ that I merge together to form another observable. This is working as expected:
The stream starts with >>>
Each value are emitted one by one
The stream ends with <<<
const foo$ = of('foo');
const bar$ = of('bar');
const baz$ = of('baz');
merge(foo$, bar$, baz$).pipe(startWith('>>>'), endWith('<<<')).subscribe(str => {
console.log(str);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.min.js"></script>
<script>const {merge, of} = rxjs; const {startWith, endWith} = rxjs.operators;</script>
Now if none of the three observables above emit a value, I do not want to output neither >>> nor <<<. So startWith and endWith can only "run" if merge(foo$, bar$, baz$) actually emits a value.
In order to simulate that I'm rejecting all values emitted by foo$, bar$ and baz$ with a filtering function.
const foo$ = of('foo').pipe(filter(() => false));
const bar$ = of('bar').pipe(filter(() => false));
const baz$ = of('baz').pipe(filter(() => false));
merge(foo$, bar$, baz$).pipe(startWith('>>>'), endWith('<<<')).subscribe(str => {
console.log(str);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.min.js"></script>
<script>const {merge, of} = rxjs; const {startWith, endWith, filter} = rxjs.operators</script>
However as you can see in the output, both startWith and endWith have emitted their value even though the merge() hasn't produced any.
Question: How can I prevent startWith and endWith from executing if the observable did not emit a single value?
The first condition is simple. You can just prepend the first emission with concatMap:
mergeMap((v, index) => index === 0 ? of('>>>', v) : of(v))
The second condition is more tricky. You want basically the right opposite to defaultIfEmpty. I can't think of any simple solution so I'd probably use endWith anyway and just ignore the emission if it's the first and only emission (which means the source just completed without emitting anything):
endWith('<<<'),
filter((v, index) => index !== 0 || v !== '<<<'),
Complete example:
const foo$ = of('foo');//.pipe(filter(() => false));
const bar$ = of('bar');//).pipe(filter(() => false));
const baz$ = of('baz');//.pipe(filter(() => false));
merge(foo$, bar$, baz$).pipe(
mergeMap((v, index) => index === 0 ? of('>>>', v) : of(v)),
endWith('<<<'),
filter((v, index) => index !== 0 || v !== '<<<'),
).subscribe(console.log);
Live demo: https://stackblitz.com/edit/rxjs-n2alkc?file=index.ts
You could assign the merged observable to a variable, take the first element from the stream and then map to the observable you want to execute when atleast one value has been emitted.
const foo$ = of('foo').pipe(filter(() => false))
const bar$ = of('bar').pipe(filter(() => false))
const baz$ = of('baz').pipe(filter(() => false))
const merged$ = merge(foo$, bar$, baz$);
merged$.pipe(
take(1),
switchMap(() => merged$.pipe(
startWith('>>>'),
endWith('<<<')
))
).subscribe(console.log);
https://stackblitz.com/edit/rxjs-phspsc
From the documentation of startWith:
Returns an Observable that emits the items you specify as arguments before it begins to emit items emitted by the source Observable.
So startWith and endWith will always run.
I dont know what your expected result should be, but if you only want to concatenate the strings for each emitted value you could use the map or switchMap operators.
EDIT:
Example with map to concat each value:
const foo$ = of('foo').pipe(filter(() => false));
const bar$ = of('bar').pipe(filter(() => false));
const baz$ = of('baz').pipe(filter(() => false));
merge(foo$, bar$, baz$).pipe(map(v => `>>>${v}<<<`)).subscribe(str => {
console.log(str);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.min.js"></script>
<script>const {merge, of} = rxjs; const {startWith, endWith, filter, map} = rxjs.operators</script>
I see that all answers are done combining operators, but doing this solution with only operators is kind of tricky, why not just create your own observable? I think this solution is the easiest one to understand. No hidden cleverness, no complex operator combinations, no subscription repetitions...
Solution:
const foo$ = of('foo')//.pipe(filter(() => false));
const bar$ = of('bar')//.pipe(filter(() => false));
const baz$ = of('baz')//.pipe(filter(() => false));
function wrapIfOnEmit$(...obs) {
return new Observable(observer => {
let hasEmitted;
const subscription = merge(...obs).subscribe((data) => {
if (!hasEmitted) {
observer.next('>>>');
hasEmitted = true;
}
observer.next(data);
},
(error) => observer.error(error),
() => {
if (hasEmitted) observer.next('<<<');
observer.complete();
})
return () => subscription.unsubscribe();
})
}
wrapIfOnEmit$(foo$, bar$, baz$).subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.min.js"></script>
<script>const {merge, of, Observable} = rxjs; const {filter} = rxjs.operators</script>
Hope this helps!
One way to achieve that is to use a materialize-dematerialize operators pair:
source$.pipe(
// turn all events on stream into Notifications
materialize(),
// wrap elements only if they are present
switchMap((event, index) => {
// if its first event and its a value
if (index === 0 && event.kind === 'N') {
const startingNotification = new Notification('N', '>>>', undefined);
return of(startingNotification, event);
}
// if its a completion event and it not a first event
if (index > 0 && event.kind === 'C') {
const endingNotification = new Notification('N', '<<<', undefined);
return of(endingNotification, event);
}
return of(event);
}),
// turn Notifications back to events on stream
dematerialize()
)
Play with this code in a playground:
https://thinkrx.io/gist/5a7a7f1338737e452ff6a1937b5fe05a
for convenience, I've added an empty and error source there as well
Hope this helps
a: 1---2-3-4--5---6
b: ------T---------
o: ------1234-5---6
Using RxJS, is there some operator that can accomplish the diagram above? I have stream A which is a random stream of events, given a stream B which has a single true event, can I have an output stream that doesn't emit anything until that true event, and then sends everything is had saved up until then and afterwards emits normally?
I thought maybe I could use buffer(), but it seems like there is no way to do a one time buffer like this with that operator.
const { concat, interval, of, from } = rxjs;
const { share, delay, toArray, takeUntil, mergeMap } = rxjs.operators;
const waitUntil = signal$ => source$ => {
const sharedSource$ = source$.pipe(share());
return concat(
sharedSource$.pipe(
takeUntil(signal$),
toArray(),
mergeMap(from)
),
sharedSource$
);
}
const stopWaiting$ = of('signal').pipe(delay(2000));
const source$ = interval(500).pipe(
waitUntil(stopWaiting$)
).subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.3/rxjs.umd.js"></script>
I think #ZahiC's solution is correct but personally I'd do it in a single chain using the multicast operator.
a$.pipe(
multicast(new Subject(), s => concat(
s.pipe(
buffer(b$),
take(1),
),
s
)),
)
multicast will basically spit the stream into two where concat will first subscribe to the first one that is buffered until b$ emits. Then it completes immediately because of take(1) and concat subscribe to the same steam again but this time unbuffered.
Here's my solution, using TypeScript:
export const queueUntil = <T>(signal$: Observable<any>) => (source$: Observable<T>) => {
let shouldBuffer = true;
return source$.pipe(
bufferWhen(() => shouldBuffer ? signal$.pipe(
tap(() => shouldBuffer = false),
) : source$),
concatMap(v => v),
);
};
and can be used like this:
a$.pipe(
queueUntil(b$)
)
All existing answers here (as of 2022/1/4) have the potential to skip/eat source notifications emitted on the same frame as the notifier emits (particularly if the source stream feeds new values back into itself). This solution supports this use case:
function waitUntil<T>(notifier$: Observable<any>): MonoTypeOperatorFunction<T> {
return (source$: Observable<T>) => {
const buffer$ = new ReplaySubject<T>();
let doBuffer = true;
source$.pipe(takeWhile(() => doBuffer, true)).subscribe(buffer$);
return notifier$.pipe(
take(1),
switchMap(() => {
doBuffer = false;
return concat(buffer$, source$);
}),
);
};
}
Is there a way to get the first and last emitted values from an observable?
const down$ = fromEvent(this.canvas, 'mousedown');
const up$ = fromEvent(this.canvas, 'mouseup');
const move$ = fromEvent(this.canvas, 'mousemove');
const drag$ = move$.pipe(
skipUntil(down$),
takeUntil(up$)
);
drag$.subscribe((e: MouseEvent) => {
console.log(e);
});
Is there a way to get the first and last values from the drag$ observable?
You can get the first and the last values from an Observable with take(1) and takeLast(1) operators.
import { range, merge, Subject } from 'rxjs';
import { take, takeLast, multicast } from 'rxjs/operators';
range(1, 10)
.pipe(
multicast(() => new Subject(), o => merge(
o.pipe(take(1)),
o.pipe(takeLast(1)),
)),
)
.subscribe(v => console.log('result', v));
In your case it looks like you could do something like this but I don't know what exactly is you goal:
down$
.pipe(
switchMap(() => move$.pipe(
takeUntil(up$),
multicast(() => new Subject(), o => merge(
o.pipe(take(1)),
o.pipe(takeLast(1)),
)),
)),
)
.subscribe(console.log);
See live demo: https://stackblitz.com/edit/rxjs6-demo-ymjoiy?file=index.ts
Sorry, I was making things too complicated. This is my final code:
const up$ = fromEvent(this.canvas, 'mouseup');
const down$ = fromEvent(this.canvas, 'mousedown');
const drag$ = down$.pipe(merge(up$));
drag$.subscribe((e: MouseEvent) => console.log(e); });
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));