Can't get data from combineLatest - Angular

ShabbyAbby :

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);
  })
)
Kurt Hamilton :

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...)

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=32002&siteId=1