I have a state object with string keys and values. Events are coming in, containing key-value pairs to change the state.
I need a debounced stream that:
validates the events and drop all modifications in the debounce cycle if they lead to an invalid state
outputs the diff to the last valid state
For example, for the initial state of {k1: "v1"}, and an event of {k2: "v2"}, output {k2: "v2"}.
But for the events: {k3: "v3"} and {k4: "invalid"}, drop both changes. So when a new event {k5: "v5"} comes in, the k3 key is still undefined.
I was able to implement it, but only by using a new Subject that keeps track of the last valid state: (jsfiddle)
const lastValidState = new Rx.Subject();
const res = modifications
.buffer(debounce)
.withLatestFrom(lastValidState.startWith(state))
.map(([mods, last]) => {
// calculate next state
return [Object.assign({}, last, ...mods), last];
}).filter(([newState]) => {
// check new state
return Object.keys(newState).every((k) => !newState[k].startsWith("invalid"));
// update Subject
}).do(([newState]) => lastValidState.next(newState)).share()
.map(([newState, last]) => {
// output diff
return Object.assign({}, ...Object.keys(newState).filter((k) => newState[k] !== last[k]).map((k) => ({[k]: newState[k]})))
}
)
This code works well, but I don't like the new Subject it introduces. I would prefer a solution that does not rely on that and use only RxJS operators.
I've tried to use pairwise, but I could not figure out how to pair a stream with the last value of itself.
Thanks to cartant's comment, using scan is the way to go.
The only trick is to use distinctUntilChanged to prevent emits for invalid changes.
The modified code: jsfiddle
const res = modifications
.buffer(debounce)
.scan((last, mods) => {
const newState = Object.assign({}, last, ...mods);
const valid = Object.keys(newState).every((k) => !newState[k].startsWith("invalid"));
if (valid) {
return Object.assign({}, ...Object.keys(newState).filter((k) => newState[k] !== last[k]).map((k) => ({[k]: newState[k]})));
}else {
return last;
}
}, state)
.distinctUntilChanged()
Related
I want combineLatest functional but for variable count of observables.
Something like:
// init combineLatest of three observables
[1, 2, 3]
// first observable produced new value "2"
[2, 2, 3]
// third observable ended
[2, 2]
// first observable produced new value "1"
[1, 2]
// new observable added
[2, 2, 4]
Is it possible in RxJS?
If I understand the problem right, the solution is pretty tricky for something that looks innocent.
I try to go step by step to explain a potential solution.
First of all we need understand that there are 3 different events that we need to manage:
the fact that one Observable completes
the fact that one Observable is added to the array which is given to combineLatest
the fact that a new array of Observables has to be passed to combineLatest, either because we are at the beginning of the processing (i.e. with the initial array) or because we have added a new Observable or because one Observable has completed
The second thing that we need to recognize is that we need to store the array of Observables we pass to combineLatest in a variable, otherwise we are not able to add or remove Obaservables from there.
Once these things are clear, we can build a solution in the form of a function that returns 2 things:
the Observable that we want to subscribe to and that should have the behavior that we are looking for
a Subject that we can use to communicate that we want to add a new Observable to the combineLatest function
The last point we need to recognize is that any time we change the list of Observable, either because we add or because we remove an Observable (because it completed), we need to run the combineLatest function with the new fresh list of Observables.
Now that all this has been clarified, this is the code of the function that returns an Observable which behaves as described
function dynamicCombineLatest(startingObservables: Observable<any>[]) {
// this is the variable holding the array of Observables
let observables = startingObservables;
// this is the array that contains the list of Observables which have been, potentially, transformed to emit
// immediately the last value emitted - this happens when a new Observable is added to the array
let observablesPotentiallyWithLastValueImmediatelyEmitted =
startingObservables;
// this is the variable holding the array of values last notified by each Observable
// we will use it when we need to add a new Observable to the list
const lastValues = [];
// this are the Subjects used to notify the 3 different types of events
const start = new BehaviorSubject<Observable<any>[]>(observables);
const add = new Subject<Observable<any>>();
const remove = new Subject<Observable<any>>();
let skipFirst = false;
// this is the chain of operations which must happen when a new Observable is added
const addToObservables = add.pipe(
tap({
next: (obs) => {
console.log("add");
// we need to make sure that the Observables in the list will immediately start to emit
// the last value they emitted. In this way we are sure that, as soon as the new added Observable emits somthing,
// the last value emitted by the previous Observables will be considered
observablesPotentiallyWithLastValueImmediatelyEmitted = observables.map(
(o, i) => {
return startWith(lastValues[i])(o);
}
);
// the new Observable is added to the list
observables.push(obs);
observablesPotentiallyWithLastValueImmediatelyEmitted.push(obs);
},
})
);
// this is the chain of operations which must happen when an Observable is removed
const removeFromObservables = remove.pipe(
tap({
next: (obs) => {
const index =
observablesPotentiallyWithLastValueImmediatelyEmitted.indexOf(obs);
console.log("remove");
// we simply remove the Observable from the list and it "last value"
observablesPotentiallyWithLastValueImmediatelyEmitted.splice(index, 1);
observables.splice(index, 1);
lastValues.splice(index, 1);
// we make sure that the Observables in the list will immediately start to emit with the last value they emitted
observablesPotentiallyWithLastValueImmediatelyEmitted = observables.map(
(o, i) => {
return lastValues[i] ? startWith(lastValues[i])(o) : o;
}
);
// we set that the first value of the new combineLatest Observable will be skipped
skipFirst = true;
},
})
);
// here we merge the 2 chains of operations so that both add and remove logic will be executed
// when the relative Subjects emit
merge(addToObservables, removeFromObservables).subscribe({
next: () => {
console.log("new start");
// we notify that a change in the Observable list has occurred and therefore we need to unsubscribe the previous "combineLatest"
// and subscribe to the new one we are going to build
start.next(observablesPotentiallyWithLastValueImmediatelyEmitted);
},
});
// this is where we switch to a new Observable, result of the "combineLatest" operation,
// any time the start Subject emits a new Observable list
const dynamicObservables = start.pipe(
switchMap((_observables) => {
const _observablesSavingLastValueAndSignallingRemove = _observables.map(
(o, i) =>
o.pipe(
tap({
next: (v) => {
// here we save the last value emitted by each Observable
lastValues[i] = v;
},
complete: () => {
// here we notify that the Observable has completed and we need to remove it from the list
remove.next(o);
},
})
)
);
console.log("add or remove");
// eventually this is the Observable created by combineLatest with the expected array of Observables
const _combineLatest = combineLatest(
_observablesSavingLastValueAndSignallingRemove
);
const ret = skipFirst ? _combineLatest.pipe(skip(1)) : _combineLatest;
skipFirst = false;
return ret;
})
);
// here we return the Observable which will be subscribed to and the add Subject to be used to add new Observables
return { dynamicObservables, add };
}
You can look at this stackblitz for an example.
Buffer and combine based on a key
Here's a slight variant of what you're asking for. It works just like mergeAll, only it keeps a buffer and emits the latest for any observable that have emitted so far.
The varient here is that you need to supply string keys for your values to get attached to. You should be able to see how to turn this into array indices if you so choose.
The reason I haven't done this with an array is because there's no much undefined behavior. For example, if the first observable completes and the second observable emits, your elements are all opaquely re-ordered.
Using keys returns control back to the caller, who can just use Object.keys() if they don't care about indices/labels for their data.
Here you are:
interface LabeledObservable<T> {
label: string,
stream: Observable<T>
}
interface CombinedLatest<T> {
[key:string]: T
}
function combineLatestAll<T>():
OperatorFunction<
LabeledObservable<T>,
CombinedLatest<T>
>
{
return source$ => defer(() => {
const buffer = {};
return source$.pipe(
mergeMap(({label, stream}) => stream.pipe(
map(v => {
buffer[label] = v;
return {...buffer};
}),
finalize(() => {
delete buffer[label];
})
))
);
});
}
Subject for new observables
If you like the idea of a subject you can use to inject new observables into your combineLatest operator, this still allows that. The only alteration needed is that you must supply unique labels for your observables. If you don't care about the labels, you can just use any ID generator pattern (Like incrementing a global id counter or something).
const startingObservables: Observable<any>[] = /*some observables */;
const add = new Subject<LabeledObservable<any>>();
add.pipe(
combineLatestAll()
).subscribe(console.log);
startingObservables.forEach((stream,i) => {
add.next({label: "" + i, stream});
});
I have read the tutorial from this link https://magarcia.io/2019/02/18/bloc-pattern-with-react-hooks
and i just dont understand how the search query to the API is triggered when _query.next is called with new search terms
see below code.
export class SearchBloc {
private _results$: Observable<string[]>;
private _query$ = new BehaviorSubject<string>('');
constructor(private api: API) {
**this._results$ = this._query$.pipe(
switchMap((query) => {
return observableFrom(this.api.search(query));
})
);**
get results$(): Observable<string[]> {
return this._results$;
}
}
const SearchInput = () => {
const searchBloc = useContext(SearchContext);
const [query, setQuery] = useState('');
useEffect(() => {
searchBloc.query.next(query);
}, [searchBloc, query]);
return (
<input
type="text"
name="Search"
value={query}
onChange={({ target }) => setQuery(target.value)}
/>
);
};
Assuming that searchblock was put in the context, and during input change the query which is a behaviour subject is assigned a new value with next();
how or why does the api query executes?
I guess I did not understand the line with
this._results$ = this._query$.pipe(
switchMap((query) => {
so maybe the question is, how did the pipe worked? did it create a method callback that will execute when next is called? and what is the assignment to result mean?
anyone that can help me make sense of it is greatly appreaciated.
Consider the following code:
It creates a stream of 5 numbers. Then it creates a second stream which is defined as a stream that has all the same numbers as the first one, only each number is incremented.
const numberStream$ = of(1,2,3,4,5);
const numbersPlus1$ = numberStream$.pipe(
map(v => v + 1)
);
numbersPlus1$.subscribe(console.log);
If you subscribe to numberStream$ you should expect to get 1,2,3,4,5.
If you subscribe to numbersPlus1$ you should expect to get 2,3,4,5,6.
Here we do the same thing with a Subject. Of course, unlike of(1,2,3,4,5), a subject lets you create a stream imperatively. Whenever I call .next on a subject, I'm saying "Make this value the next emission in this subject's stream."
const numberSubject$ = new Subject<number>();
const numbersPlus1$ = numberSubject$.pipe(
map(v => v + 1)
);
numbersPlus1$.subscribe(console.log);
numberSubject$.next(1);
numberSubject$.next(2);
numberSubject$.next(3);
numberSubject$.next(4);
numberSubject$.next(5);
Is there a better way to re-write this code and avoid chaining of subscriptions ?
Why am I chaining? because I need to the output of source1$ in child subscriptions
And also I have if conditions because I want to call child subscriptions conditionally
PS i checked solution in this post
Here is the stackblitz link and code
import { from } from 'rxjs';
//emit array as a sequence of values
const source1$ = from([1]);
const source2$ = from([2]);
const source3$ = from([3]);
const useCond1 = true; // this is dynamic can be false too
const useCond2 = true; // this is dynamic can be false too
source1$.subscribe(val => {
if (useCond1) {
source2$.subscribe(() => {
console.log('val from source1 in source2', val);
});
}
if (useCond2) {
source3$.subscribe(() => {
console.log('val from source1 in source3', val);
});
}
});
Not sure, but it seems that you need switchMap or mergeMap and iif
from rxjx doc:
import { fromEvent, iif, of } from 'rxjs';
import { mergeMap, map, throttleTime, filter } from 'rxjs/operators';
const r$ = of(`I'm saying R!!`);
const x$ = of(`X's always win!!`);
fromEvent(document, 'mousemove')
.pipe(
throttleTime(50),
filter((move: MouseEvent) => move.clientY < 210),
map((move: MouseEvent) => move.clientY),
mergeMap(yCoord => iif(() => yCoord < 110, r$, x$))
)
.subscribe(console.log);
Yes, there is a better way!
RxJS provides many different operators and static functions for combining, filtering, and transforming observables. When you use what the library provides, you do not need to have nested subscriptions.
In general, I find it simpler to not do any logic at all inside the subscribe, but rather design observables that emit the exact data that is needed.
A simplistic example could look like this:
someValue$ = source1$.pipe(
switchMap(val1 => useCond1 ? source2$ : of(val1))
);
someValue$.subscribe();
switchMap will subscribe to an "inner observable" whenever it receives an emission. The logic above says to either return the value emitted from source1$ (val1) or return whatever source2$ emits depending on the value of useCond1.
So source2$ will only get subscribed to when useCond1 is true;
Note: the function inside switchMap should return an observable (because switchMap subscribes to it), so of was used to turn the emitted value into an observable.
In your case, let's assume you want to emit some calculated value, based possibly on the other two sources.
We can use combineLatest to create a single observable based on 3 different sources. Since you only want to optionally call source2$ and source3$, we can define the sources based on your conditions. We can then use map to transform the array of values from the 3 sources, into the desired output:
someValue$ = source1$.pipe(
switchMap(val1 => {
const s1$ = of(val1);
const s2$ = useCond1 ? source2$ : of('default val2');
const s3$ = useCond2 ? source3$ : of('default val3');
return combineLatest([s1$, s2$, s3$]);
}),
map(([val1, val2, val3]) => {
return ... // your logic to return desired value
})
);
combineLatest will emit an array containing the latest emissions from each source whenever any source emits. This means someValue$ will emit the latest calculated value whenever any of the sources change.
I have a situation where I have an observable, and for each emitted item, I want to create another observable, but ignore that observable's value and instead return the result of the first observable.
For example, if I click a button, I want to track something that happens in another button, only when the first button is toggled on.
I can do this now, sort of, with a hack, by taking the output of the child observable and piping it to a mapTo with the parent's value. You can see it in this code, which can be played with in a code sandbox:
import { fromEvent, from } from "rxjs";
import { mapTo, switchMap, tap, scan } from "rxjs/operators";
const buttonA = document.getElementById("a");
const buttonB = document.getElementById("b");
const textA = document.querySelector('#texta');
const textB = document.querySelector('#textb');
fromEvent(buttonA, 'click').pipe(
// this toggles active or not.
scan((active) => !active, false),
switchMap(active => {
if (active) {
const buttonBClicks$ = fromEvent(buttonB, 'click');
// here we can observe button b clicks, when button a is toggled on.
return buttonBClicks$.pipe(
// count the sum of button b clicks since button a was toggled on.
scan((count) => count+1, 0),
tap(buttonBCount => {
textB.value = `button b count ${buttonBCount}`;
}),
// ignore the value of the button b count for the final observable output.
mapTo(active)
)
} else {
textB.value = ``;
return from([active]);
}
})
).subscribe({
next: buttonActive => {
textA.value = `Button a active: ${buttonActive}`
}
});
A couple issues here. In the case that the button is toggled on, the outer observable only receives a value once the button is clicked.
This mapTo use seems hacky.
Any better ways to do this?
It sounds like you don't want the inner observable to actually be a part of the process at all. Are you waiting on it or anything?
If not, you can just do it all as a side effect as follows:
fromEvent(buttonA, 'click').pipe(
scan((active) => !active, false),
tap(active => { if(active) {
fromEvent(buttonB, 'click').pipe(
scan(count => count+1, 0),
tap(buttonBCount => {
textB.value = `button b count ${buttonBCount}`;
})
).subscribe()
}})
).subscribe({
next: buttonActive => {
textA.value = `Button a active: ${buttonActive}`
}
});
Nested subscriptions are considered bad voodoo, so you ca refactor like this to keep your separation of conserns more apparent:
const trackActiveFromButton$ = fromEvent(buttonA, 'click').pipe(
scan((active) => !active, false),
shareReplay(1)
);
trackActiveFromButton$.subscribe({
next: buttonActive => {
textA.value = `Button a active: ${buttonActive}`
}
});
trackActiveFromButton$.pipe(
switchMap(active => active ?
fromEvent(buttonB, 'click').pipe(
scan(count => count+1, 0),
tap(buttonBCount => {
textB.value = `button b count ${buttonBCount}`;
})
) :
EMPTY
)
).subscribe();
Any better ways to do this?
The below may be better depending on your taste. It seems to me your sample code gets a little messy because you have a single observable that is trying to do too many things. And the side-effects are sort of mixed in with the stream behavior logic.
It's totally fine to be use tap() to do side-effect type things, but sometimes it can make it harder to follow. Especially in the above code, since there is a nested observable involved.
Creating separate observables that always emit specific data can make things easier to follow.
If we declare a stream to represent the isActive state and subscribe to that to update textA, and define a counter stream to represent the number of clicks that occurred while isActive = true, using that value to update textB, I think it makes it easier to follow what's going on:
const clicksA$ = fromEvent(buttonA, 'click');
const clicksB$ = fromEvent(buttonB, 'click');
const isActive$ = clicksA$.pipe(
scan(active => !active, false),
startWith(false)
);
const counterB$ = combineLatest([isActive$, clicksB$]).pipe(
scan((count, [isActive]) => isActive ? count + 1 : -1, 0)
);
counterB$.subscribe(
count => textB.value = count === -1 ? '' :`button b count ${count}`
);
isActive$.subscribe(
isActive => textA.value = `Button a active: ${isActive}`
);
To me, having the streams defined separately makes it easier to see the relationship between them, meaning, it's easier to tell when they will emit:
isActive derives from clicksA
counterB derives from clicksB & isActive
Here's a working StackBlitz
Also:
the outer observable only receives a value once the button is clicked
This can be solved using startWith() to emit a default value.
I need to create an observable, which I can "pull" data from, to work with a pageable api. I can only fetch 100 items per request, I want to be able to use observable as a generator function (on which I can call .next() to issue a request to get next 100 items.
I can't unfortunately find a way to do it with Rx. I suppose it's possible using controlled observable or a subject. Can you guys show me an example.
this is what I've gotten so far:
function list(entityType, viewName, fetchAll = false) {
var skip = 0,
total = 0;
const subject = new Rx.Subject(),
response$ = subject
.takeWhile(() => skip <= total)
.startWith(skip)
.flatMap((skip) => fetchPagePromise(skip)),
next = () => subject.onNext(skip);
if (fetchAll) {
Rx.Observable.timer(100, 100).subscribe(() => next());
}
return {
data$: response$.map(response => response),
next: fetchAll === true ? undefined : next
};
function fetchPagePromise() {
let limit = 100,
obj = {
viewName, limit, skip
},
qs = objectToQueryString(obj);
return $http.get(`${apiBase}/api/data/${entityType}${qs}`).then((res) => {
total = res.data.Total;
skip += limit;
return res.data.Rows;
});
}
}
this kinda works like a generator. it returns an Observable and next handler. Whenever next is called it pulls next 100 items from api and pushes into the Observable. Also if there’s a third parameter fetchAll passed, then it will keep fetching data until there’s no more. What scares me though that there are 2 mutating vars in function's closure - skip and total, and I don't know if managing them like this in asynchronous/unpredictable environment is ok.
One of the things you generally want to avoid is trying to make Rx into a plain old event emitter. Usually it is an indicator when you try and just trigger Observables manually by passing around a Subjects observer interface.
You should ask yourself, where is my data coming from? What calls next(), what calls that, etc. After enough of these you will generally find that this will lead you to something that can be wrapped by an Observable directly rather than explicitly calling next(). Also, I think the fetchAll flag should really be kept externally. You are only making the interface confusing by essentially turning it into a void method just by passing in a flag.
So I would recommend refactoring like so:
Rx.Observable.prototype.lazyRequest = function(entityType, viewName, limit = 100) {
var source = this;
return Rx.Observable.create(obs => {
var response = source
//Skip is really just the (limit * index)
.map((x, i) => i * limit)
.flatMap((skip) => {
let obj = {viewName, skip, limit},
qs = objectToQueryString(obj);
//Handle promises implicitly
return $http.get(`${apiBase}/api/data/${entityType}${qs}`);
},
//Return this with our skip information
(skip, res) => {skip, res})
//Publish it so the stream get shared.
.publish();
//This will emit once once you are out of data
var stop = response.first(x => x.skip >= x.res.data.Total);
return new CompositeDisposable(
//Complete this stream when stop emits
response.takeUntil(stop)
//Downstream only cares about the data rows
.map(x => x.res.data.Rows)
.subscribe(obs),
//Hook everything up
response.connect());
});
}
Then you can use it like so:
//An example of a "starting point", a button click
//Update the rows every time a new event comes through
Rx.Observable.fromEvent($button, 'click')
.startWith(0) //Inject some data into the pipeline
.lazyRequest(entityType, viewName)
.subscribe(/*Do something with the returned rows*/);
//Get all of the rows, will keep hitting the endpoint until it completes
Rx.Observable.interval(100)
.lazyRequest(entityType, viewName)
//Gather all the values into an array and emit that.
.toArray()
.subscribe();