0

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.

1
  • 1
    You need to subscribe to the Observable with eg. subscribe() Commented Jul 24, 2018 at 16:42

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

Comments

0

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}`)));
}

1 Comment

For observables which are "short-lived" (those derived from http requests are short-lived), they self-terminate once the HTTP request is complete. For "long lived" observables (such as those which can emit multiple items e.g. a service notifying of some sort of changing thing) you must unsubscribe, or provide some other method to terminate the observable (operators like takeWhile, takeUntil, etc) when the component is destroyed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.