Home > other >  Angular map wait Observable to complete
Angular map wait Observable to complete

Time:09-25

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

CodePudding user response:

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.

CodePudding user response:

The RxJS way:

Though it's strange to hit an api endpoint and ignore it's result, you can be sure to wait on that result using just about any of the RxJS higher-order operators (like mergeMap, switchMap, concatMap, etc).

Since you're doing this for an entire array, forkJoin is a natural choice. forkJoin will subscribe to all your observables and emit an array of the last value for each once they complete.

test() {
  // mergeMap here is silly, it doesn't merge or map anything in a 
  // meaningful way. I've removed it.
  this.test2().subscribe(
    students => console.log(students[0])
  );
}

test1(): Observable<Student[]> {
  return of(this.getStudents());
}

test2(): Observable<Student[]> {

  return this.test1().pipe(

    map(students => students?.map(student => {
      if(student.age == 1){
        return this.client.get(
          'https://api.coindesk.com/v1/bpi/currentprice.json'
        ).pipe(
          tap(currentPrice => console.log(currentPrice)),
          mapTo({...student, age: student.age * 1000})
        )
      }else{
        return of({...student, age: student.age * 10})
      }
    })),

    mergeMap(studentCalls => forkJoin(studentCalls))

  );
}

getStudents(): Student[] {
  return [{ age: 1 }, { age: 2 }, { age: 3 }];
}
  • Related