RxJS operator for left joining 2 observables - javascript

I want to left join 2 Observables such that I always take the value from ObservableA, unless there is a corresponding value from ObservableB. In which case I want to use the value from ObservableB. The two Observables should join so will be a akin to a SQL left join.
From reading the RxJS Operators tree it does seem to me that Join would be the logical choice. I am unclear on how the join window works and how to ensure either observable could result in a join happening.
Can anyone provide a simple example?

You can do this easily with just merge where you put ObservableB first and then use take(1) to complete immediately. If ObservableB was empty then it'll emit the value from ObservableA.
const a$ = of(1);
const b$ = of('a');
merge(b$, a$)
.pipe(
take(1),
)
.subscribe(console.log);
Live demo: https://stackblitz.com/edit/rxjs6-demo-u5syjx?file=index.ts

There seems to be no out of the box operator for this. But you may want to zip first, then map the results with your custom logic to get what you want:
zip(observableA, observableB).map([a,b] =>{
if(idMatchesWithB) {return b}
else {return a}
}

Related

How to properly flat this Observable array?

I need to flat the results of a Observable<Order[]>[] in a Order[].
The current way that I'm doing it:
const ordersObservable: Observable<Order[]>[] = [];
//ordersObservable is populated with a bunch of Observable<Order[]>
forkJoin(ordersObservable)
.pipe(
map((results) => ([] as Order[]).concat(...results))
)
.subscribe((orders: Order[]) => {
this.orderService.set(orders);
//...
});
I've been told that I shouldn't be using pipe like that and should be using a RxJs function to handle that.
I've tried to use concatAll and mergeAll instead of the map, but that ended in calling the orderService for every item in ordersObservable, instead a single time with a flat array with all results of ordersObservable.
What am I doing wrong? And how can I flat the results of all the observables in a single array, preferably with a native RxJs solution?
An Observable is meant to emit values. With an array of Observables, we need to subscribe to each element of the Observable array to emit the Order array.
I honestly think you should re-evaluate creating an array of Observables. If you want to post another question with the code that is generating that array of Observables, we could provide suggestions.
That said, I was able to get something to work. I didn't spend the time to see if there was an easier approach.
from(this.ordersObservable)
.pipe(
concatAll(),
scan((acc, value) => [...acc, ...value], [] as Order[]),
takeLast(1)
)
.subscribe((x) => console.log('result', JSON.stringify(x)));
First, I needed something to subscribe to. We can't subscribe to the array of Observables. So I used from to turn the Array of Observables into another Observable. That way I could subscribe and get the code to execute.
The from emits each Observable from the array.
NOTE: forkJoin and combineLatest also work instead of from and provide the same result.
I then use concatAll() to concatenate the inner Observables in sequence. It subscribes to each Observable in the array.
scan allows us to define an accumulator, accumulating each array of Orders into a single array of orders.
UPDATE: Added takeLast(1) to ensure only the last result is emitted.
UPDATE 2: Second option
This also worked and may be a bit simpler:
concat(...this.ordersObservable)
.pipe(
scan((acc, value) => [...acc, ...value], [] as Order[]),
takeLast(1)
)
.subscribe((x) => console.log('result', JSON.stringify(x)));
This uses array destructuring (the "...") with the concat operator to emit all of the values from all of the Observables in the array of Observables. Then we still use scan to accumulate them and takeLast(1) to emit only once.
I would be interested to know if anyone else is able to simplify this!

Multiple asynchronous string replace in RxJS

In an Angular app I have:
a text in which I want to translate several resource keys
an array of resource keys ("IS_LESS_THAN", "IS_GREATER_THAN", etc...)
an array of languages ("EN, NL, FR", etc..)
a service that can translate a single resource key. The service facilitates that when the chosen language changes, it emits a new value through the observable.
Asynchronous is the complex part. I cannot do a simple string.replace() on the text with a for loop because that would only work in a synchronous way.
How can the translation be accomplished using RxJS?
I have the following code, but stuck at the end:
const text = '....... 3 IS_LESS_THAN 5 ........';
const keys = ['IS_LESS_THAN', 'IS_GREATER_THAN', ....... ];
const translatedText$ = of(keys).pipe(
switchMap((key) =>
this.localizationService.translate(key).pipe( // Will update its value when the current language changes
map((value) => ({
// Store the original key/value combination to do the replace
key,
value,
}))
)
// PROBLEM: How to replace each key in the text and return the result?
// I tried reduce, but how to combine the accumulator and the value?
reduce((acc, val) => {
return val[0].replace(val[1], );
})
You need to provide the initial seed value, which in your case is probably text.
reduce((translatedText, translation) => {
return translatedText.replace(translation.key, translation.value);
}, text)
You should also think about changing switchMap to mergeMap if you want all of your keys to be translated: https://ncjamieson.com/avoiding-switchmap-related-bugs/
It was easier than expected. The catch was to combine the translation results first, using combineLatest. From that point, all translations are available in a substitutions list in a single element of the observable. It is no longer needed to reduce multiple observable items:
return combineLatest(
keys.map((key) =>
this.localizationService.translate(key)))
).pipe(map((substitutions) => replace(text, substitutions)));

Why rxjs print data one single character every time?

This is my code in RxJs6:
const observable$ = interval(1000).pipe(
take(2),
map(x => interval(1500).pipe(
map(y => x+':'+y),
take(2),
concatAll()
)),
);
observable$.subscribe(obs => {
obs.subscribe(x => console.log(x));
});
I expect my code show the result like this:
0:0
1:0
0:1
1:1
But it actually shows:
why my code print data only one character every time ? And I think it should work like what i expected above not the actual result. anything wrong i understand about rxjs ?
This is because of concatAll(). It's typically used to flatten nested Observables but it can work with Promises and arrays (array-like objects) as well. Ant this is exactly what you're seeing here.
It thinks you want to flatten an array even when you have a string so it takes each item in the array (character in your case) and reemits it separately.
However, another question is what you wanted to achieve with concatAll.

RxJS 1 array item into sequence of single items - operator

Given such observable
Rx.Observable.of([1,2,3,4,5])
which emits a single item (that is an array), what is the operator that will transform this observable to a one that emits 5 single items (or whatever the array consists of)?
The example is on .of, but it would be the same for fetching arrays via promises, there might be many other examples. Don't suggest to replace of with from
I can't think of an existing operator to do that, but you can make one up :
arrayEmitting$.concatMap(arrayValues => Rx.Observable.merge(arrayValues.map(Rx.Observable.of)))
or the simpler
arrayEmitting$.concatMap(Rx.Observable.of)
or the shortest
arrayEmitting$.concatMap(x => x)
That is untested so let me know if that worked for you, and that uses Rxjs v4 API (specially the last one). This basically :
process each incoming array of values as one unit (meaning that the next incoming array will not interlap with the previous one - that is why I use concatMap)
the incoming array is transformed into an array of observables, which are merged : this ensures the emission of values separately and in sequence
You can also use the flatMap operator (https://stackoverflow.com/a/32241743/3338239):
Observable.of([1, 2, 3, 4])
.flatMap(x => x)
.subscribe(x => console.log(x));
// output:
// 1
// 2
// 3
// 4
You can use from now to convert an array into a sequence.
https://www.learnrxjs.io/operators/creation/from.html
from([4,5,6])
mergeAll:
Observable.of([4, 5, 6])
.mergeAll()
.subscribe(console.log);
// output:
// 4
// 5
// 6
Dont understand why concatMap(x => x) or flatMap(x => x) works, it doesnt change anything.
This should work (rxjs 6 or above):
of([4,5,6]).pipe(mergeMap(from))

RXJS How to convert Observable<T[]> to Observable<T>

I would like to take an Observable<T[]> and convert it to an Observable<T> such that each array from the Observable<T[]> is broken up and the individual elements of the arrays are then emitted, separately, via the Observable<T>.
Is there a standard operator for doing this? I've searched around but haven't found anything. Thanks.
After being pointed in the direction of concatMap/flatMap, I came up with the following general solution:
var source: Observable<T[]>;
...
var splitSource = source.flatMap<T>((x:T[]) => { return Rx.Observable.fromArray(x); });
You could use concatMap like this:
function identity (x) {return x}
var obs_of_array$ = Rx.Observable.return(['1','2','you got it'])
.concatMap(identity)
This works because concatMap also accepts arrays (and observables and promises) as the return value of the function selector that you pass as a parameter. Jsbin here, documentation here
So the array passed as parameter becomes the return value from the selector function, and then is flattened by the concatMap operator while respecting the ordering of the array.
I've also been playing around with converting Observable<T[]> to Observable<T>.
I've found that flatMap() does the job on it's own, which I presume was not the case 11 months ago when the question was asked.
To illustrate, in the jsfiddle in #user3743222's answer, you can make this change and get the same output:
//.concatMap(identity)
.flatMap(theArray=>theArray)
I use this and the toArray() operator to perform set operations in the pipeline, for example to sort the observable.
let sorted$ = source$.toArray().flatMap(theList=> theList.sort(this.myComparer) )
The output is Observable<T>. (N.B works in the context of a fixed array, not sure what would happen with a continuously generating source)

Categories

Resources