In my service I try to solve the next problem. I have a json file with the names of other json files (cards):
{
"filename1" : ... ,
"filename2" : ... ,
...
"filenameN" : ...
}
I need to load all that files. "filenameX" files have some card data :
{
dataX
}
I need to combine loaded data in object:
{
"filename1" : { data1 },
"filename2" : { data2 },
...
"filenameN" : { dataN }
}
I create Observer for any file loading and try to combine them to a high-level single Observer which is resolved when all corresponding Observers are. Here is my code:
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/concat";
import "rxjs/add/operator/map";
...
_loadCards(dir, cardsListFile) {
var http = ...
var url = ...
return Observable.create(function (observer) {
http.get(url + dir + "/" + cardsListFile + ".json").map(res => { return res.json(); }).subscribe(list => {
let data = {};
let observers = [];
for(var card in list) {
if(list.hasOwnProperty(card)) {
let obs = http.get(url + dir + "/cards/" + card + ".json").map(res => { return res.json(); });
observers.push(obs);
let getData = function(card) {
obs.subscribe(cardData => {
data[card] = cardData;
});
};
getData(card);
}
}
let concatResult = Observable.concat(observers);
console.log(concatResult);
concatResult.subscribe(result => {
observer.onNext(data);
observer.onCompleted();
});
});
});
};
However, concat operator of Observer doesn't work as described - it returns the same array of Observers as its input. Where is the problem and what other operators can I use to make my solution more straight forward (because now it's defenitely ugly)?
I'm not completely sure I understand what you want to do but the
concat()
operator takes its parameters unpacked and not as an array.This means that
Observable.concat(observables)
will just reemit the Observables as they are in theobservables
array.What you want instead is pass the array unpacked
Observable.concat(...observables)
which is the same as:I think this simulates what you're trying to do. It's uses
forkJoin
instead ofconcat
so it waits until all of the Observables complete.This prints to console:
See live demo: https://jsbin.com/coruyu/4/edit?js,console
JSBIN with solution: http://jsbin.com/dahaqif/edit?js,console
I'm assuming you have two observables, one to get the list of filenames, the other to get the data for a specific filename:
Now, here's how you can combine the two observables to obtain the data structure you described:
If you subscribe and log the result to the console, you'll see:
Additional clarifications following Arseniy's comment
mergeMap()
is a "trick" to transform the SINGLE array of values we have at the beginning into MULTIPLE, INDIVIDUAL values. Since you want to be able to process each filename individually (to fetch the corresponding data), it's more convenient to receive the filenames one by one vs as a big array containing all filenames. To make it crystal clear, you should go to my JSBIN and add the line.do(console.log)
before and after the firstmergeMap()
. You'll immediately understand the difference.mergeMap()
"projects" the values of a source observable into a target observable. It sounds fancy but it simply means we are transforming the filenames (1st observable) into HTTP requests to get the files' data (2nd observable).Object.assign()
lets met put everything back together. Since we have data coming from two observables — the one emitting the filenames and the one emitting the file data — we need to merge everything into a single container before we can use it (or transform it further). For this I'm usingObject.assign()
to create a temporary object with two properties,filename
anddata
. Note that is vanilla JavaScript, it has nothing to do with observables. Again, you can add a.do(console.log)
line after the secondmergeMap()
to print out to the console what the stream contains at this point.Rule #1: Never use custom subscriptions inside other subscriptions - there is maybe 0.1% of cases where this is really needed.
I've rewritten your stream a little how it could be done:
An adjusted version of this to run live as a snippet: