'How to get a certain post, its tags and comments from Wordpress REST API using switchMap and then combine the resulting values using JS?

I am working on an Angular app that needs to fetch a certain post with a given ID, its respective tag names and comments from Wordpress REST API. This application works much like StackOverflow's question-answer-logic, so I will be referring to the Wordpress post as a question and the Wordpress comments as answers. With the API three GET requests need to be sent to the server. The first request is being sent to /wp/v2/posts/<id>. The response from the server looks like this:

Excerpt of post request:

{
    "id": 9,
    "title": {
        "rendered": "Hello World! 3"
    },
    "tags": [
        2,
        3,
        4
    ]
}

The numbers in the tags arrays aren't the actual human-readable tag names, they're just the identifiers for the tags. After we have received the first response from the server, we need to send another GET request to the server to /wp/v2/tags?include=2,3,4 while supplying the tag identifiers with it. We don't need every single tag available in the database, just the ones that were referenced to in the response to the first request. In other words, the second request depends upon the results of the first request. The second request's response from the server looks like this:

Excerpt of tags request:

[
    {
        "id": 8,
        "name": "test"
    },
    {
        "id": 9,
        "name": "test2"
    },
    {
        "id": 30087,
        "name": "test3"
    }
]

The third and final request needs to be sent to /wp/v2/comments?post=9. This will limit result set to comments assigned to the specific post ID. The response from the server looks like this:

Excerpt of comments request:

[
   {
      "id":3,
      "post":4,
      "content":{
         "rendered":"<p>This is an example comment number 3.<\/p>\n"
      }
   },
   {
      "id":2,
      "post":9,
      "content":{
         "rendered":"<p>This is an example comment number 2.<\/p>\n"
      }
   }
]

Angular RxJS' switchMap operator seems to be the way to go. My requirements are as follows:

  • call posts/3 (post id number is gathered from URL parameter https://angular-ivy-jhuhth.stackblitz.io/detail/3)
  • take ID from post
  • call tags with ID
  • call comments with ID
  • using vanilla JavaScript merge tags and comments with posts and return (so that the combined object has a key called tag_names and comments' content and date)

then .subscribe() to the overall flow and get a combined object of the post, its tags and comments which can then be iterated over in the Angular template.

EDIT: In the template I need to display title, date, content and human-readable tag names for the question/post and also content and date for the answer/comment.

My code in Stackblitz so far looks like this. Forks to my code would be greatly appreciated. At the tome of coding the solution, I experienced a console error: questions.map is not a function on line 44 in detail.component.ts. I need to fix this in order to continue the work.

Two concepts used in the code:

  • forkJoin RxJS operator, which basically waits for all the Observables to complete and then combines the result.
  • async pipe, which subscribes to an Observable or Promise and returns the latest value it has emitted


Solution 1:[1]

Reason for the error?

getQuestion() method in the service deals with an object instead of an array of objects, as shown below.

/** GET specific post from the server based on given ID */
getQuestion(id: number): Observable<Question> {
    return this.http.get<Question>(`${this.questionsUrl}/posts/${id}`);
}

questions.map is not a function error is thrown in detail.component.ts in the map RxJS operator section, because we are trying to call Array.map() method for an object, which doesn't work.

Solution

The error can be fixed by creating a temporary array and pushing the switchMap parameter variable question into that temporary array. After that, the array can be passed as an argument in the of RxJS operator. All of this will be done in the component's getQuestion function, as shown below:

switchMap((question: Question) => {
    const tagIDs = question.tags.join(',');
    const questionID = +question.id;

    // create temporary empty array
    const tempArray = [];
    tempArray.push(question);

    return forkJoin({
        // reference the temporary array in the of operator call
        questions: of(tempArray),
        tags: this.questionService.getTags(tagIDs),
        comments: this.questionService.getDetailsAnswers(questionID),
    });
})

This is a bit crude, but effective and manages to solve the issue.

Finally, the data can be displayed in the template with the following code:

<div class="container m-5">
    <div *ngFor="let data of combined$ | async">
    <h1>{{ data.title.rendered }}</h1>
    <p>Asked {{ data.date | date: 'medium' }}</p>
    <hr />
    <p [innerHTML]="data.content.rendered"></p>
        <span *ngFor="let tag_name of data.tag_names">
    <a routerLink="/search/{{ tag_name }}/1" class="badge bg-secondary">{{
    tag_name }}</a>&nbsp; </span>
    <br /><br />
    <p>{{ data?.comments?.length }} answers</p>
    <div *ngFor="let answer of data.comments">
        <p [innerHTML]="answer?.content?.rendered"></p>
        <p class="text-end">Answered {{ answer?.date | date: 'medium' }}</p>
    <hr />
</div>

StackBlitz here: https://stackblitz.com/edit/angular-ivy-puctmm?file=src%2Fapp%2Fdetail%2Fdetail.component.ts

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 aleksejjj