switchMapTo upon source observable emission - rxjs

The difference between switchMap and switchMapTo is that switchMap transforms each source emission into observable upon the emission and switchMapTo ignores emitted value and transforms each emission to an Observable that is built up during stream creation.
The thumb rule here is use switchMap when your inner stream depends on the source stream values and use switchMapTo when it doesn't.
But what if I don't care for the emission value but I do care for the emission time?
Meaning I'd like the inner Observable to be evaluated upon source Observable emission.
The obvious thing here is to use switchMap(() => Observable) but it just doesn't feel right, because of the thumb rule I mentioned before.
Example with switchMapTo (bad):
const predefinedKey = 'key';
//This need to be initialized
const obj = {};
function getObservable(key){
return Rx.Observable.of(obj[key]);
}
//This is initialization stream
const initialize = new Rx.ReplaySubject();
initialize.next(1);
const onInit = initialize.do(val => obj[predefinedKey] = val);
//Would like to access the object only after initialization
const result = onInit.switchMapTo(getObservable(predefinedKey));
//Expect to see 1 in output but see 'undefined' because switchMapTo evaluated before the object is initialized
result.subscribe(val => console.log(val));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.min.js"></script>
Example with switchMap (good):
const predefinedKey = 'key';
//This need to be initialized
const obj = {};
function getObservable(key){
return Rx.Observable.of(obj[key]);
}
//This is initialization stream
const initialize = new Rx.ReplaySubject();
initialize.next(1);
const onInit = initialize.do(val => obj[predefinedKey] = val);
//Would like to access the object only after initialization
const result = onInit.switchMap(() => getObservable(predefinedKey));
//Expect to see 1 in output
result.subscribe(val => console.log(val));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.min.js"></script>
The examples are very artificial but they describe the situation pretty well.
What is the right approach here? Any other Observable function I can use for delayed execution?

Based on your example, you can use switchMapTo in combination with Observable.defer:
const predefinedKey = 'key';
const obj = {};
function getObservable(key){
return Rx.Observable.defer(() => Rx.Observable.of(obj[key]));
}
const initialize = new Rx.ReplaySubject();
initialize.next(1);
const onInit = initialize.do(val => obj[predefinedKey] = val);
const result = onInit.switchMapTo(getObservable(predefinedKey));
result.subscribe(val => console.log(val));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.min.js"></script>
Instead of deferring in getObservable, you could also defer in the switchMapTo call:
const result = onInit.switchMapTo(Rx.Observable.defer(() => getObservable(predefinedKey)));
This will just depend on the situation. That said, I also don't think there's anything wrong with using switchMap and personally, I'd probably do that instead of deferring (which is useful in other situations).

Related

combineLatest with variable count of observables

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

rxjs collects the values sent by Subject and then sends the past values as an array

rxjs collects the values sent by Subject and then sends the past values as an array
import { Subject } from "rxjs";
const list$ = new Subject<number>();
list$
.pipe(/* Which pipeline should I use to achieve the following log information */)
.subscribe(console.log);
list$.next(1); // log: [1]
list$.next(2); // log: [1,2]
In RxJS .pipe() is where you list a series of operators. Each operator is a pure functinon that receives the emitted value from the previous observable (or operator), and returns a new value for the next operator.
Because of the utility that operators provide, the subscription method can be kept rather simple, or completely empty.
Given your requirement to convert each emitted value to an array, I would recommend the scan() operator. Similar to Array.reduce(), the scan() operator gives you an aggregate value you can add to for each emission from the source observable (in this case, your subject).
const list = new Subject<number>();
list.pipe(
scan((numArray, number)=>
[...numArray, number], [] as number[]
)
).subscribe(numArray=>console.log(numArray));
list.next(1); // [1]
list.next(2); // [1,2]
The reason I didn't include $ in the variable name, is that this is typically reserved only for public observables (not subjects or subscriptions, which shouldn't be exposed to other classes/modules/files).
I customized Observable to solve the problem, but I want to know how to use the pipe that comes with rxjs
import { Observable, Subject } from "rxjs";
const list$ = new Subject<number>();
var observable = new Observable((subscribe) => {
const cache = [];
const sub = list$.subscribe((v) => {
cache.push(v);
subscribe.next(cache);
});
return function unsubscribe() {
sub.unsubscribe();
};
});
const sub = observable.subscribe(console.log);
list$.next(1); // log: [1]
list$.next(2); // log: [1,2]

is there a Better way rather than to chain subscribe inside a subscribe with an if condition

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.

Observable with inner observables

I have been working with Observables a bit, but can't figure out how to do the following:
I have an observable that emits an array of IDs.
I then want to retrieve these IDs, again using Observables.
Finally I want to put everything together in an Observable that
emits an array of the retrieved items.
I could put my code attempts here, but I don't think it would help a lot.
I'm using RxJS 5.5
You can use for that flattening operators for that (like mergeMap or concatMap depending on requirements). For example:
const { Observable, combineLatest, of, from } = rxjs; // = require("rxjs")
const { mergeMap, toArray, map } = rxjs.operators; // = require("rxjs/operators")
const ids = [0,1,2,3,4,5,6,7,8,9];
const transform = id => of(`${id}-transformed`);
of(ids).pipe(
mergeMap(ids => ids),
mergeMap(id => transform(id)),
toArray()
).subscribe(e => console.log(e))
<script src="https://unpkg.com/rxjs#6.2.2/bundles/rxjs.umd.min.js"></script>

reselect CreateStructuredSelector difference in properties

Maybe this is not necessarily a reselect question
const makeSelectError = () => createSelector(
selectGlobal,
(globalState) => globalState.get('error')
);
and in reselect we use
const mapStateToProps = createStructuredSelector({
error: makeSelectError(),
});
why can't we use like below?
const makeSelectError = createSelector(
selectGlobal,
(globalState) => globalState.get('error')
);
and use like below in reselect
const mapStateToProps = createStructuredSelector({
error: makeSelectError,
});
Are there any issues/disadvantages with my code, or is that a standard practice?
Not only is the second way valid and correct it has other advantages.
With the first snippet you provide:
const makeSelectError = () => createSelector(
selectGlobal,
(globalState) => globalState.get('error')
);
makeSelectError is a factory function in that every time it is called it is returning a new and unique selector.
This means that every time a simple mapStateToProps function is called a new selector will be made and the result of the selector will be computed again.
This means that you will be losing the key benefit of reselect that is memoization.
So for simple cases you could just do the following:
const getSomePieceOfState = state => state.someArea.someDetail;
const getAnotherPieceOfState = state => state.anotherArea.anotherItem;
const getSomeCombinedState = createSelector(
getSomePieceOfState,
getAnotherPieceOfState,
(somePiece, anotherPiece) => somePiece + anotherPiece
);
const mapStateToProps = state => ({
someProp: getSomeCombinedState(state)
});
Note. it's common to prefix the name of selectors with get and to prefix the name of a selector factory (a function that returns a selector) with makeGet.
Some times creating a selector factory is necessary though if you wan't to make a selector that is dependant on a property that is not in the state.
You can read more about that here Accessing React Props in Selectors
You're doing it absolutely right in the second example. This is a standard practice.
There's no need to do wrap makeSelectError to another function.

Resources