I have the following code
import { fromEvent } from 'rxjs';
import { debounceTime, map, mergeMap, tap } from 'rxjs/operators';
// Examples: kumar
const url = 'https://gorest.co.in/public/v2/users?name=';
// elem ref
const searchBox = document.getElementById('search');
// streams
const keyup$ = fromEvent(searchBox, 'keyup');
keyup$
.pipe(
map((i: any) => i.currentTarget.value),
debounceTime(500),
mergeMap((v) => fetch(url + v)),
tap((result) => {
console.log('JSON: ', result.json());
})
)
.subscribe((result) => console.log(result));
You can copy and paste it into https://stackblitz.com/edit/typescript-adheqt?devtoolsheight=50&file=index.ts
But I don't see the JSON result. Please help, I don't understand why I don't see any data.
PS: I don't want to use fromFetch. It's just for learning purposes and I would like to use the fetch api.
json() returns a promise so you still need to resolve that promise/observable :
keyup$
.pipe(
map((i: any) => i.currentTarget.value),
mergeMap((e) => fetch(`https://gorest.co.in/public/v2/users?name=${e}`)),
mergeMap(response => response.json()), // json() resolved using mergeMap
debounceTime(500)
)
.subscribe(console.log);
Related
I want to implement a progress bar with rxjs,so I need to get the position of mousedown and the position of mousemove after mousedown.here is the test code I write.
useEffect(() => {
const start$ = fromEvent(divRef.current, 'mousedown').pipe(
tap(() => console.log('start')),
map(event => [event.clientX, event.clientY])
)
const move$ = fromEvent(divRef.current, 'mousemove').pipe(
tap(() => console.log('move')),
map(event => [event.clientX, event.clientY])
)
const end$ = fromEvent(divRef.current, 'mouseup')
const drag$ = concat(start$, move$).pipe(takeUntil(end$), repeat())
const subscription = drag$.subscribe(([newX, newY]) => {
setX(newX)
setY(newY)
})
return () => {
subscription.unsubscribe()
}
})
what I think is that when I click down the mouse,I will subscribe the Start$,and then if I move,I will subscribe the move$.But the Phenomenon is quiet different from what I thought.The log in the console just output 'start'.
enter image description here
as you can see from the picture,when I click down and move,I just can subscribe the start$,if I use the concat method wrong.Hope someone can do me a favor.
I assume you want the mouse movements in the order they happen. Here is a small snippet:
import './style.css';
import {
tap,also
fromEvent,
takeUntil,
exhaustMap,
} from 'rxjs';
const mouseDown$ = fromEvent(document, 'mousedown');
const mouseMove$ = fromEvent(document, 'mousemove');
const mouseUp$ = fromEvent(document, 'mouseup');
mouseDown$
.pipe(
tap(console.log),
exhaustMap((start) =>
mouseMove$.pipe(tap(console.log), takeUntil(mouseUp$))
)
)
.subscribe();
Please have a look here: Stackblitz
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.
In the example you can see that in case you need an initial value for a stream of the void type it looks incorrect.
import { of, fromEvent, concat, Subject } from "rxjs";
import { map, switchMap, take, finalize } from "rxjs/operators";
let sequence = 1;
const trigger = new Subject<void>();
const source = concat(
of(""), // is there any way to make it more pretty?
trigger
).pipe(
switchMap(() =>
fromEvent(document, "click").pipe(
take(3),
finalize(() => {
sequence++;
trigger.next();
})
)
),
map(
({ clientY }: MouseEvent) => `sequence: ${sequence} clientY: ${clientY}!`
)
);
source.subscribe(x => console.log(x));
Not sure if there is a "prettier" solution than this:
const source =
trigger.pipe(
startWith(undefined as void),
switchMap(...),
...
);
I'm looking for a way to buffer values of an observable until some other observable has emitted, but then emit all the previous values. Something like skipUntil, if skipUntil also emitted skipped values as soon as the second observable emitted.
--a--b----c-----d---e--- (source)
-----------1------------- (other1)
------------abc-d---e----(desired output)
You can use bufferWhen:
import { fromEvent, interval } from 'rxjs';
import { bufferWhen } from 'rxjs/operators';
const clicks = fromEvent(document, 'click');
const buffered = clicks.pipe(bufferWhen(() =>
interval(1000 + Math.random() * 4000)
));
buffered.subscribe(x => console.log(x));
Here's the custom operator I came up with. Not sure if it can be done in a prettier way.
export function bufferUntil(stopBufferSignal: Observable<any>) {
return <T>(source: Observable<T>): Observable<T> => {
return source.pipe(buffer(stopBufferSignal),
take(1),
flatMap(ar => {
const sourceWithNSkipped$: Observable<T> = source.pipe(skip(ar.length));
const bufferedItems$: Observable<T> = from(ar);
return bufferedItems$.pipe(concat(sourceWithNSkipped$))
}));
}
}
I'm using a library that exposes data from a service class using a pretty common BehaviorSubject pattern. The only notable difference with the implementation and what I have seen/used myself is the addition of a pipe with a shareReplay(1) operator. I'm not sure if the shareReplay is required. What effect, if any, does the shareReplay have in this case?
// "rxjs": "^6.3.0"
this.data = new BehaviorSubject({});
this.data$ = this.data.asObservable().pipe(
shareReplay(1)
)
Note: I've read a number of articles on shareReplay, and I've seen questions about different combinations of shareReplay and Subject, but not this particular one
Not in your example but imagine if there was some complex logic in a map function that transformed the data then the share replay would save that complex logic being run for each subscription.
const { BehaviorSubject } = rxjs;
const { map, shareReplay } = rxjs.operators;
const bs$ = new BehaviorSubject('initial value');
const obs$ = bs$.pipe(
map(val => {
console.log('mapping');
return 'mapped value';
}),
shareReplay({bufferSize:1, refCount: true})
);
obs$.subscribe(val => { console.log(val); });
obs$.subscribe(val => { console.log(val); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.1/rxjs.umd.min.js"></script>
Compare without the share, the map happens twice.
const { BehaviorSubject } = rxjs;
const { map } = rxjs.operators;
const bs$ = new BehaviorSubject('initial value');
const obs$ = bs$.pipe(
map(val => {
console.log('mapping');
return 'mapped value';
})
);
obs$.subscribe(val => { console.log(val); });
obs$.subscribe(val => { console.log(val); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.1/rxjs.umd.min.js"></script>
With this pattern (use of shareReplay(1)), the service protects itself from the user using the next() function of the BehaviorSubject while sending the last value of the BehaviorSubject (which would not have been the case without shareReplay(1)).