0

I'm building an Angular app to show my website's articles and votes and I want to show them sorted based on their vote. I can retrieve votes making http calls, in particular:

  1. I have to make a http call to retrieve the list of categories and the list of their IDs (I often add categories in my website so I need to update my app)
  2. I have to use these IDs to make one http call for each ID to get the list of articles of that particular category.
  3. Every article has an ID, I have to store every ID to make another one http call for each article ID.
  4. Finally every n.3) response object has a 'users_vote' property. I need then to build an array to merge all the votes and then sort the array. Then I'll show the array on HTML code.

I don't understand how to chain promises so the code is just to clarify what I mean. I also know that it's really 'tricky' but I can't change anything server side at the moment and I have to deal with this.

 ngOnInit() {
    this.getCategories()
    .then((res) => {
      this.getIds(res)
      .then((res) => {
        this.getArticlesList(res)
        .then((res) => {
          this.mergeArticlesList(res)
        })
      })
    })
  }


// 1) getCategories() to get the list 

  getCategories() {
      var promise = new Promise((resolve) => {
        this.myService.getCategoriesList()
        .subscribe((res) => {
          this.categories = res;
          resolve(this.categories);
        })
      })
      return promise;
    }

// 2) getCategoriesIDs(res) to get their IDs and store them in this.ids

  getCategoryIDs(res) {
    var promise = new Promise((resolve) => {
      for (let i=0; i < Object.keys(res).length; i++) {
        this.ids[i] = res[i+1]['id']
      }
      resolve(this.ids)
    })
    return promise;
  }

// 3) getArticlesList(res) to get the list of articles of each category

  getArticlesList(res) {
    var promise = new Promise((resolve) => {
      for (let i=0; i < Object.keys(res).length; i++) {
        this.myService.getArticlesOfCategory(res[i])
        .subscribe((res) => {
          this.allarticleslist[i] = res;
        })
      }
      resolve(this.allarticleslist)
    })
    return promise;
  }

// 4) mergeArticleIds(res) to create another array of ids (this time is // the list of articles' IDs

  mergeArticleIds(res) {
    console.log("Start merging with:")
    console.log(res)
    console.log(res.length)
    console.log(Object.keys(res).length)
    for (let i=1; i < Object.keys(res).length -1; i++) {
      for (let _i=0; _i < res[i]['reviews'].length; _i++) {
        this.mergedArticles = res[i]['reviews'][_i]['id']      }
    }
    return this.mergedArticles
  }

// 5) getArticle(res) to get the article with that particular ID
// With this http call I finally can get 'article_vote' and I have to 
// save this value in order to sort the array. 
// It has to return the array sorted

  getArticle(res) {
    // ??
 }

}

consolelog

It's not working because console.log(res.length) returns 0, so for loop doesn't even start. I know I have to deal with asynchronous operations timing too, but I don't know how to do it.

EDIT: adding getCategoriesList() from myService:

getCategoriesList(): Observable<any[]> {
    let url = 'myurl';
    return this.httpClient.get(url)
}

EDIT2: adding getCategoriesList()'s http.get response:

{"5":{"description":"","title":"SOCCER""term_id":280,"room_name":"SOCCER"},
"4":{"description":"","title":"NFL","term_id":281,"room_name":"NFL"},
"3":{"description":"","title":"MLB","term_id":282,"room_name":"MLB"},
"2":{"description":"","title":"NHL","term_id":283,"room_name":"NHL"},
"6":{"description":"","title":"TENNIS","term_id":284,"room_name":"TENNIS"},
"1":{"description":"","title":"F1","term_id":285,"room_name":"F1"}}
Federico
  • 415
  • 1
  • 3
  • 15
  • Are you not using Observables? By default, Angular HTTPClient returns an observable. If you want then we can provide a solution using Observable which you can convert it to promises. Let us know – user2216584 Jun 17 '19 at 15:16
  • You have no `console.log` in the code you're showing here, and all of the functions use the variable name `res` somewhere in them, so it's not clear which one is showing undefined... As @user2216584 mentioned, HttpClient returns an Observable, which has a `toPromise()` method on it, so you never have to create new promises like you are. You can also just use Observable to do what you need. – Heretic Monkey Jun 17 '19 at 15:19
  • If you are going to use promises, there are several posts on Stack Overflow about how to chain them, including [Dynamic Chaining in Javascript Promises](https://stackoverflow.com/q/30853265/215552), [Javascript Angular: how to chain unknown number of promises](https://stackoverflow.com/q/23560570/215552) – Heretic Monkey Jun 17 '19 at 15:21
  • I've added some logs to let you understand what I mean...the http calls returns observable, it was my mistake, I'm sorry – Federico Jun 17 '19 at 15:52
  • @user2216584 it's okay, I can use Observables too – Federico Jun 17 '19 at 16:09
  • @Federico - I have proposed a solution with Observable. Let us know if it works for you. – user2216584 Jun 17 '19 at 16:31

2 Answers2

2

You can easily chain promises by creating an array of promises like that

let promiseArray: Promise<any>[] = [];

Then you can add Promise function by using push

var promise = new Promise();
promiseArray.push(promise);

You can use Promise.all(promiseArray).then((responses) => { ... }) it will resolved all promises in your array

I hope it will help you !

sebaferreras
  • 44,206
  • 11
  • 116
  • 134
Toothgip
  • 469
  • 6
  • 14
2

Please try following code -

// assuming that this.myService.getCategoriesList() returns an observable which has all categories
    this.myService.getCategoriesList()
        .pipe(
          switchMap(categories => {
            // NOTE - You should use category Id; I dont know the property which represents categoryId;
            // Replace cat.categoryId with your ID of a category
            const observables = categories.map(cat => this.myService.getArticlesOfCategory(cat.categoryId));
            return forkJoin(observables);
          }),
          switchMap(arrayOfArticlesByCategoryId => {
            // NOTE - You should use article Id; I dont know the property which represents articleId;
            // Replace article.articleId with your ID of an article
            const observables = arrayOfArticlesByCategoryId.map(article => this.myService.getArticle(article.articleId));
            return forkJoin(observables);
          }),
          map(arrayOfallArticles => {
            //You can also use 'pluck' operator - https://www.learnrxjs.io/operators/transformation/pluck.html
            return arrayOfallArticles.map(article => article.users_vote);
          }),
          map(arrayOfUsersVote => {
            console.log(arrayOfUsersVote);
            // Do whatever you want to do with arrayOfUsersVote i.e. sort them
          })
        ).subscribe();
user2216584
  • 5,387
  • 3
  • 22
  • 29
  • Thanks a lot! However it throws error "categories.map is not a function"...I've read it could be because of the type of categories and in fact it is the only one of type 'any'...any idea? I imported map and switchMap from rxjs/operators, is it right? – Federico Jun 17 '19 at 17:00
  • @Federico I have assumed that `this.myService.getCategoriesList()` returns Observable [i.e. Array of Category] wrapped in an observable. If its not correct then you have to adjust the code as per return type of this.myService.getCategoriesList(). – user2216584 Jun 17 '19 at 17:02
  • I've updated the question with my service's method...it's the first time I deal with Observables so I'm trying to understand how they work. Should I create something like a class Categories? – Federico Jun 17 '19 at 17:20
  • @Federico Your updated code looks fine. Just make sure that your backend returns the list of categories. If it returns an array then the code which I suggested should work. – user2216584 Jun 17 '19 at 17:25
  • you were right, getCategoriesList() returns an Object but I can't work with backend for a while, so should I convert that Object into an array? – Federico Jun 17 '19 at 17:36
  • @Federico Yes, if it is possible to change the Object to an Array, the do it. BTW - Can you show the value returned by the server? – user2216584 Jun 17 '19 at 17:40
  • Yes, I've updated it. Which is the best way to convert it into an Array? – Federico Jun 17 '19 at 17:59
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/195078/discussion-between-user2216584-and-federico). – user2216584 Jun 17 '19 at 18:10
  • It's quite simple. Iterate through the object properties and construct your Category class objects – user2216584 Jun 17 '19 at 18:12