Alexander
Alexander

Reputation: 109

Multiple rxjs http requests Angular 6

I have following code:

private getUsers(page, result) {
result = result||[];
return this.http.get(API_URL + '/users?page=1')
.pipe(map(response => {
  const response_filter = response.json();
  const users = response_filter['data'];
  const pages = response_filter['total_pages'];
  Array.prototype.push.apply(result, users.map((user) => new User(user)));
  while (page != pages)
  {
    this.http.get(API_URL + '/users?page=' + page)
    .pipe(map(resp => {
      console.log('test');
      const response_filter = resp.json();
      const users = response_filter['data'];
      Array.prototype.push.apply(result, users.map((user) => new User(user)));
      return result;
    }))
    .pipe(catchError(val => of(`Caught inner error: ${val}`)));
    page += 1;
  }
  return result;
}))
.pipe(catchError(val => of(`Caught error: ${val}`)));
}

Code works good until console.log('test'). This log doesn't get shown, but while loop iterates fine. Previously i tried the same function, but in recursive way. There was the same problem.

Upvotes: 0

Views: 1236

Answers (2)

Alexander
Alexander

Reputation: 109

Marks answer is great, but I already solved my problem (maybe not in the good way, but solved it) using Martin comment (using subscribe). Firstly I subscribe for a "get pages count" request and then I'm subscribing to "get users" request in a while loop.

I'm new in angular, so maybe someone will answer a question "Must I use unsubscribe here?"

this._dataSub0 = this.userDataService.getPages().subscribe((pages) => {
  var page_num = pages;
  var i = 1;
  while (i < page_num) {
    this._dataSub = this.userDataService
      .getAllUsers()
      .subscribe(
        (users) => {
            for (let us of users) {
              this.users.push(us);
            }
        }
      );
    i++;
  }
});

public getAllUsers(page): Observable<User[]> {
return this.getUsers(page);
}
private getUsers(page) {
  var result = result||[];
  return this.http.get(API_URL + '/users?page=' + page)
  .pipe(map(response => {
    const response_filter = response.json();
    const users = response_filter['data'];
    const pages = response_filter['total_pages']
    if(pages == page)
      return null;
    Array.prototype.push.apply(result, users.map((user) => new User(user)));
    return result;
  }))
  .pipe(catchError(val => of(`Caught error: ${val}`)));
}
public getPages(): Observable<number> {
  var result;
  return this.http.get(API_URL + '/users?page=0')
  .pipe(map(response => {
    const response_filter = response.json();
    const pages = response_filter['total_pages']
    return pages;
  }))
  .pipe(catchError(val => of(`Caught error: ${val}`)));
}

Upvotes: 0

Mark Hughes
Mark Hughes

Reputation: 7374

The best way to do this is to create a single observable which represents all of the requests you want to make, using flatMap and forkJoin operators. There are a number of problems with the asynchronous operations in your code, meaning that the returned result will not include the results of the inner HTTP requests.

I would propose the following:

private getUsers(page, result) {
    return this.http.get(API_URL + '/users?page=1')
    .pipe(
        flatMap((response) => {
            const response_filter = response.json();
            const users = response_filter['data'];
            const pages = response_filter['total_pages'];
            let firstPageUsers: User[] = users.map((user) => new User(user));
            let getAllUsers: Observable<User[]>[];
            getAllUsers.push(of(firstPageUsers));
            while (page < pages)
            {
                getAllUsers.push(this.http.get(API_URL + '/users?page=' + page)
                   .pipe(
                       map(resp => {
                           console.log('test');
                           const response_filter = resp.json();
                           const users = response_filter['data'];
                           return users.map((user) => new User(user));
                       }),
                       // You need to decide if this is how you want errors
                       // handled, it doesn't seem too sensible to me:
                       catchError((err) => {
                           console.log(`Caught inner error: ${err}`);
                           return of([]); // needs to return type Observable<User[]>
                       })
                   )
                );
                page += 1;
            }
            return forkJoin(getAllUsers);
        }),
        map((allResponses) => {
            // allResponses will be an array of User arrays from 
            // all of the observables within the forkJoin, so now
            // we can iterate over all of those to create a single 
            // array containing all of the results.
            result = result||[];
            allResponses.forEach((responseUsers) => {
                 Array.prototype.push.apply(result, responseUsers);
            });
            return result;
        }),
        catchError((err) => {
            console.log(`Caught outer error: ${err}`);
            of(null); // Or whatever - again, think about your error cases.
        })
     );
  }

Now wherever you are calling getUsers, when you subscribe to this observable it should resolve all of the inner queries as well.

Upvotes: 2

Related Questions