I'm trying to get files included in logs of a post. What am I doing wrong here? Data is not coming later in chain when I'm trying to pipe
result of combineLatest
. The whole code is used in data resolver service.
return this.API.getPost(route.params.id).pipe(
switchMap((response: any) => {
if (response["logs"]) {
response["logs"].map(logs => {
if (logs["files"]) {
return combineLatest(
...logs["files"].map(file=>
this.API.getFile(file.id)
)
).pipe(
// Not getting any files from this.API.getFile(file.id)
map(files =>
files.map(file => ({
url: this.sanitizer.bypassSecurityTrustResourceUrl(
window.URL.createObjectURL(file)
)
}))
)
),
map(files => {
logs["files"] = files;
return response;
});
}
});
}
return of(response);
})
)
Your problem
You're not returning the combineLatest
observable to your switchMap
.
Inside your if
block in switchMap
, you are just creating an unused map of observables.
In a very simplified way, you are currently doing this:
switchMap(response =>
if (response["logs"]) {
response["logs"].map(logs => of(logs));
}
return of(response);
}
I've simplified your combine latest into an of
to demonstrate the problem. When the if
condition passes, the block will create a new array, which is then immediately ignored. This means that regardless of your if condition, switchMap
will always invoke of(response)
. Your combineLatest
array will never run.
A solution
You need to return some kind of observable from your if
block. If you think about the data type you are creating, it is an array of observables. So for that you will need a forkJoin
to run an array of observables and return a single observable that switchMap
can switch to.
return this.API.getPost(this.route.params.id).pipe(
switchMap((response: any) => {
if (response["logs"]) {
return forkJoin(response["logs"].map(logs => {
if (logs["files"]) {
return combineLatest(
...logs["files"].map(file=>
this.API.getFile(file.id)
)
).pipe(
map(files =>
files.map(file => ({
url: this.sanitizer.bypassSecurityTrustResourceUrl(
window.URL.createObjectURL(file)
)
}))
)
),
// Not sure what this is???
map(files => {
logs["files"] = files;
return response;
});
}
}));
}
return of(response);
})
)
Additionally, I'm not sure what the purpose of the map
is that I've commented - it's currently serving no purpose, and it may even cause compilation issues.
DEMO: https://stackblitz.com/edit/angular-5jgk9z
This demo is a simplistic abstraction of your problem. The "not working" version creates a map of observables that it doesn't return. The "working" version switches to a forkJoin
and returns that. In both cases, the condition guarding the if
block is true.
Optimising the observable creation
I think the creation of the inner observables can be simplified and made safer.
It seems a little redundant to wrap an array of combineLatest
in a forkJoin
, when you can just use forkJoin
directly.
And to make it clearer, I would separate the array mapping from the observable creation. This would also help you avoid bugs where you end up with an empty array going into combineLatest
or forkJoin
.
// inside the "if" block
// flatten files
const files = response["logs"]
.filter(log => !!log.files)
.map(log => log.files)
.flat();
if (files.length > 0) {
const observables = files.map(file => this.API.getFile(file.id));
return forkJoin(observables).pipe(
map(files => {
files.map(file => ({
url: this.sanitizer.bypassSecurityTrustResourceUrl(
window.URL.createObjectURL(file)
)
}))
})
);
}
This uses the .flat()
array function which takes a multi-dimensional array like:
[
[1,2,3],
[4,5,6]
]
and flattens it out to this:
[ 1,2,3,4,5,6 ]
If you need IE support and don't have a polyfill, you can use an alternative from here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat.
I would also recommend creating some interfaces and using strong typing. You soon start to get lost if you're relying solely on good variable naming (not that we ever give variables bad names, of course...)