skip to Main Content

I have a form with multiple posts.
User has the option to comment on one post and submit or comment on multiple posts and submit.

The problem which I’m facing is that I have just one API to save user comment.

for eg:- http://localhost:4200/post/postid

To let user know the status in case of saving all comments, is there a way to show loading with %age for each successful / failed response.

I’m currently looking to solve using Rxjs operators but other solution is also appreciated.

Thanks in advance 🙂

This is an example of a post

{
  post: {
    "userId": 1,
    "postId": 1,
    "title": "Hello World",
    "body": "post body will contain large text"
    "pictures": ["1.jpg", "2.jpg"],
    "date": "01/01/1970",
    "lastModifiedDate": "01/02/1970"
  }
}

For storing multiple comments at once I run a loop times the count of post to push all post in an array and use forkJoin to call the API.

This works but takes a lot of time to finish because forkJoin completes only after all subscriptions have completed.

posts = [
  post: {
    "userId": 1,
    "postId": 1,
    "title": "Hello World",
    "body": "post body will contain large text"
    "pictures": ["1.jpg", "2.jpg"],
    "date": "01/01/1970",
    "lastModifiedDate": "01/02/1970"
  },
  post: {...}
]


let list = [];
for(let i=0; i<posts.length; i++){
  list.push({postObject});
}

forkJoin(list).subscribe(response => {
   console.log(response);
}

2

Answers


  1. You could use merge instead of forkJoin. See doc.

    You will get the result of each call one by one.

    yourLoaderPercentage=0;
    ...
    
    merge(list).subscribe(response => {
       console.log(response);
       this.yourLoaderPercentage += 1/list.length;
    }
    
    Login or Signup to reply.
  2. You can create an observable that emits an object that represents the state of your "progress". A simple interface could look like this

    {
      total: number;
      completed: number;
    }
    

    We can start the observable stream with the individual requests. I’d use a Subject to push single requests through something like this:

    private request$ = new Subject<your type>();
    
    makeRequest(data) {
      this.request$.next(data);
    }
    

    Then, we can create an observable that does your desired behavior:

    • make each individual post request
    • emit the desired status as requests are made and when responses are received

    To achieve updating the state twice for each request (once upon firing the request, and once when receiving the response) we need an observable that emits twice. We can achieve using the startWith operator like this:

    private post(data) {
      return this.http.post(data).pipe(
        map(response => ({ ...response, isComplete: true})),
        startWith({isComplete: false})
      );
    }
    

    We use startWith to immediately emit a value so we can update the overall state before the response comes back. Notice the isComplete flag. It’s set to false for the initial emission and set to true for the emission that occurs after the response is received. Later on we need a way to determine which property of our state object to update.

    With this in mind, we can now create our observable based on the response$ stream. We will use the scan operator to maintain our state. scan take two parameters:

    • a function that receives the prior value and the current emission
    • seed value that serves as the "prior value" when handling the first emission

    Each time scan receives an emission, it will run the function and emit the result.

      requests$ = this.request$.pipe(
        mergeMap(request => this.post(request)),
        scan(({total, completed}, request) => ({
          total: total + (!request.isComplete ? 1 : 0),
          completed: completed + (request.isComplete ? 1 : 0),
        }), { total: 0, completed: 0 }),
      );
    

    Explaination:

    • mergeMap – will execute this.post() each time it receives an emission from request$, then it will emit the response. (remember this.post() emits two values for each request).
    • scan – receives emissions from this.post() and runs the provided function to calculate a value based on the current emission and prior emissions. We return an object with two properties total and completed. When the emission is denoted as isCompleted = false, we increment the total. When isCompleted = true we increment the completed count.

    Now, you can simply consume the requests$ observable in the template using the async pipe:

        <ng-container *ngIf="requests$ | async as requests">
          <progress [value]="requests.completed / requests.total"> </progress>
          <label> {{ requests.completed }} / {{ requests.total}} </label>
        </ng-container>
    

    Here is a StackBlitz example you can play with.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search