Search code examples
angulartypescriptrxjsobservablehttpclient

Angular map wait Observable to complete


I am trying to send httpclient in my Observable function, it will run without the HttpClient Complete.

Here is a demo code which is used to reproduce

    test() {
    this.test2()
      .pipe(
        mergeMap((result) => {
          console.log(result[0]);
          return of([]);
        })
      )
      .subscribe();
  }
  test1(): Observable<any> {
    return of(this.getStudents());
  }
  test2(): Observable<any> {
    return this.test1().pipe(
      mergeMap((result) => {
        if (result) {
          result.map((rr) => {
            if (rr.age == 1) {
              this.client
                .get('https://api.coindesk.com/v1/bpi/currentprice.json')
                .subscribe((res) => {
                  console.log(res);
                  rr.age = rr.age * 10000;
                });
            } else {
              rr.age = rr.age * 10;
            }
            return rr;
          });
        }
        return of(result);
      })
    );
  }
  getStudents(): Student[] {
    return [{ age: 1 }, { age: 2 }, { age: 3 }];
  }

This is Student

export class Student {
  age: number;
}

For expected result, the console.log(res); should return before console.log(result[0]);.

I have tried many ways like .toPromise, and async await, but fail to make it work.

You could fork a test version below:

https://stackblitz.com/edit/save-file-to-drive-7dawzd?file=src/app/app.component.ts


Solution

  • Based on your expected result, you need the api call to finish first before printing the Student[0].

    The issues on your code:

    • You are subscribing to an api call which you are not waiting to finish, hence console.log(result[0]) prints first before console.log(res); because the api call isn't done yet.

    I used a couple of RXJS operators to get what you want.

    • mergeMap to flatten the inner observable
    • of to convert the student array into an observable
    • map to transform the current array into a a new array with their corresponding new age
    • forkJoin - we wrapped multiple requests into one observable and will only return when a response has been received for all requests.

    This is certainly only one way to do it and there might be other better ways.

    test() {
      this.test2()
        .pipe(
          mergeMap((result) => {
            console.log(result);
            if (result && result.length > 0) {
              console.log(result[0]);
            }
            return of([]);
          })
        )
        .subscribe();
    }
    test1(): Observable < Student[] > {
      // return of(null);
      return of(this.getStudents());
    }
    test2(): Observable < Student[] > {
      return this.test1().pipe(
        mergeMap((result) => {
          if (!result) {
            return of(result);
          }
          return forkJoin(
            result.map((rr) =>
              rr.age === 1 ?
              this.client
              .get('https://api.coindesk.com/v1/bpi/currentprice.json')
              .pipe(
                map((res) => {
                  console.log(res);
                  return (rr.age = rr.age * 10000);
                })
              ) :
              of ((rr.age = rr.age * 10))
            )
          ).pipe(
            map((paths) => {
              return result.map((e, index) => ({
                age: paths[index],
              }));
            })
          );
        })
      );
    }
    
    getStudents(): Student[] {
      return [{
        age: 1
      }, {
        age: 2
      }, {
        age: 3
      }];
    }
    

    I modified the stackblitz you created to mock the solution.