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!
Related
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)));
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.
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}
}
[{"creationDate":"2011-03-13T00:17:25.000Z","fileName":"IMG_0001.JPG"},
{"creationDate":"2009-10-09T21:09:20.000Z","fileName":"IMG_0002.JPG"}]
[{"creationDate":"2012-10-08T21:29:49.800Z","fileName":"IMG_0004.JPG",
{"creationDate":"2010-08-08T18:52:11.900Z","fileName":"IMG_0003.JPG"}]
I use a HTTP get method to receive data. Unfortunately, while I do receive this data in chunks, it is not sorted by creationDate DESCENDING.
I need to sort these objects by creationDate my expected result would be.
[{"creationDate":"2012-10-08T21:29:49.800Z","fileName":"IMG_0004.JPG"},
{"creationDate":"2011-03-13T00:17:25.000Z","fileName":"IMG_0001.JPG"}]
[{"creationDate":"2010-08-08T18:52:11.900Z","fileName":"IMG_0003.JPG"},
{"creationDate":"2009-10-09T21:09:20.000Z","fileName":"IMG_0002.JPG"}]
Here's what I tried:
dataInChunks.map(data => {
return data.sort((a,b)=> {
return new Date(b.creationDate).getTime() - new Date(a.creationDate).getTime();
});
})
.subscribe(data => {
console.log(data);
})
This works only but only 1 object at a time which results in giving me the very top result. I need some way to join these chunks together and sort them and in some way break the whole object again into chunks of two.
Are there any RSJX operators I can use for this?
If you know the call definitely completes (which it should) then you can just use toArray which as the name suggests returns an array, which you can then sort. The point of toArray is that it won't produce a stream of data but will wait until the observer completes and return all values:
var allData = dataInChunks.toArray().sort(/*sorting logic*/);
However, if you are required to show the data in the browser as it arrives (if the toArray() approach makes the UI feel unresponsive), then you will have to re-sort the increasing dataset as it arrives:
var allData =[];
dataInChunks
.bufferWithCount(4)
.subscribe(vals => {
allData = allData.concat(vals);
allData.sort(/* sort logic*/);
})
This is slightly hacky as it's relying on a variable outside the stream, but yet get the idea. It uses a buffer bufferWithCount which will allow you to limit the number of re-sorts you do.
TBH, I would just go with the toArray approach, which begs the question why it's an observable in the first place! Good luck.
I'm trying to get into reactive programming. I use array-functions like map, filter and reduce all the time and love that I can do array manipulation without creating state.
As an exercise, I'm trying to create a filterable list with RxJS without introducing state variables. In the end it should work similar to this:
I would know how to accomplish this with naive JavaScript or AngularJS/ReactJS but I'm trying to do this with nothing but RxJS and without creating state variables:
var list = [
'John',
'Marie',
'Max',
'Eduard',
'Collin'
];
Rx.Observable.fromEvent(document.querySelector('#filter'), 'keyup')
.map(function(e) { return e.target.value; });
// i need to get the search value in here somehow:
Rx.Observable.from(list).filter(function() {});
Now how do I get the search value into my filter function on the observable that I created from my list?
Thanks a lot for your help!
You'll need to wrap the from(list) as it will need to restart the list observable again every time the filter is changed. Since that could happen a lot, you'll also probably want to prevent filtering when the filter is too short, or if there is another key stroke within a small time frame.
//This is a cold observable we'll go ahead and make this here
var reactiveList = Rx.Observable.from(list);
//This will actually perform our filtering
function filterList(filterValue) {
return reactiveList.filter(function(e) {
return /*do filtering with filterValue*/;
}).toArray();
}
var source = Rx.Observable.fromEvent(document.querySelector('#filter'), 'keyup')
.map(function(e) { return e.target.value;})
//The next two operators are primarily to stop us from filtering before
//the user is done typing or if the input is too small
.filter(function(value) { return value.length > 2; })
.debounce(750 /*ms*/)
//Cancel inflight operations if a new item comes in.
//Then flatten everything into one sequence
.flatMapLatest(filterList);
//Nothing will happen until you've subscribed
source.subscribe(function() {/*Do something with that list*/});
This is all adapted from one of the standard examples for RxJS here
You can create a new stream, that takes the list of people and the keyups stream, merge them and scans to filter the latter.
const keyup$ = Rx.Observable.fromEvent(_input, 'keyup')
.map(ev => ev.target.value)
.debounce(500);
const people$ = Rx.Observable.of(people)
.merge(keyup$)
.scan((list, value) => people.filter(item => item.includes(value)));
This way you will have:
-L------------------ people list
------k-----k--k---- keyups stream
-L----k-----k--k---- merged stream
Then you can scan it. As docs says:
Rx.Observable.prototype.scan(accumulator, [seed])
Applies an accumulator function over an observable sequence and returns each
intermediate result.
That means you will be able to filter the list, storing the new list on the accumulator.
Once you subscribe, the data will be the new list.
people$.subscribe(data => console.log(data) ); //this will print your filtered list on console
Hope it helps/was clear enough
You can look how I did it here:
https://github.com/erykpiast/autocompleted-select/
It's end to end solution, with grabbing user interactions and rendering filtered list to DOM.
You could take a look at WebRx's List-Projections as well.
Live-Demo
Disclosure: I am the author of the Framework.