1

What can I do to make resolver wait until it gets images from the API. Right now, Angular waits until data is received, shows page and then tries to get images of the post.

@Injectable()
export class DataResolverService implements Resolve<any> {
  constructor(
    private router: Router,
    private API: ApiService
  ) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<any> | Observable<never> {
    return this.API.getPostById(route.params.id).pipe(
      map(response => {
        if (response["images"]) {
          const images = [];
          response["images"].forEach(image => {
            this.API.getImageById(image.id).subscribe(
              (img: any) => {
                const imageObject = {
                  url: window.URL.createObjectURL(img),
                };
                images.push(imageObject);
              }
            );
          });
          response["images"] = images;
          return response;
        }

        return response;
      })
    );
  }
}
Fanalea
  • 181
  • 1
  • 2
  • 18
  • 1
    It looks like you have embedded Observable's; so the resolver resolves after the first one resolves. I suspect you want to return the inner observable not the outer observable. There should be an rxjs operator to take care of this. Possibly MergeMap? – JeffryHouser Mar 05 '20 at 12:52
  • 1
    You are correct. SwitchMap helped me to resolve the issue. – Fanalea Mar 05 '20 at 13:39

2 Answers2

3

Try:

@Injectable()
export class DataResolverService implements Resolve<any> {
  constructor(
    private router: Router,
    private API: ApiService
  ) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<any> | Observable<never> {
    return this.API.getPostById(route.params.id).pipe(
      switchMap(response => { // change this into a switchMap
        if (response["images"]) {
          return combineLatest(
            // combine all of the request for the images
            ...response["images"].map(image => this.API.getImageById(image.id)),
          ).pipe(
            map(images => images.map(image => ({ url: window.URL.createObjectURL(image) })),
            map(images => ({ response: images })), // This map may be unnecessary
          );
        } else {
         return of([]);
        }
      })
    );
  }
}

That should get you started. The issue with your approach is that you're subscribing in the inner observable and I don't think the route resolver is waiting for it.

AliF50
  • 16,947
  • 1
  • 21
  • 37
  • 1
    It worked like a charm. Thank you for your help. The last map helped me to append images to response. – Fanalea Mar 05 '20 at 13:38
0

You are returning "response" before your asynchronous method "this.API.getImageById(image.id)" resolve. You must wait for all asynchronous iterations to end before returning. Theere are some options to make this, this is on option:

return this.API.getPostById(route.params.id).pipe(
      map(async response => {
        if (response["images"]) {
          const images = [];
          response["images"].forEach(image => {
           await this.API.getImageById(image.id).then(
              (img: any) => {
                const imageObject = {
                  url: window.URL.createObjectURL(img),
                };
                images.push(imageObject);
              }
            );
          });
          response["images"] = images;
          return response;
        }

        return response;
      })
    );
Matheus Carvalho
  • 415
  • 7
  • 13