0

The application I am working on uses a NgRx Store, and I am triggering my API calls via @ngrx/effects. I think my problem can be best explained by imagining a simple to-do list. The following is a simplification of the current state of one of my effects.

persistItem$ = createEffect(() =>
   this.actions$.pipe(
      ofType(ListActions.ActionType.PersistItem),
      mergeMap(({ listItem }) => {
        return this.listApiService.modifyListItem(listItem).pipe(
          map((updatedItem) => ListApiActions.persistItemSuccess({ updatedItem })),
          catchError((error) => of(ListApiActions.persistItemFailure({ failedItem: mergeItemWithError(listItem, error) })))
        );
      })
   )
);

When the user checks an item from the list and instantly unchecks it again (because they f.e. made a mistake) now two parallel requests are on their way. Since the backend wants to prevent multiple users from overwriting other changes, versioning is applied. Due to this the second request will fail, because it still contains the old version number.

enter image description here

So for this to work the client has to wait for the request to finish and afterwards send the second request.

The problem with sending only one request at a time is, that this would also apply if two seperate list items are edited, which should be possible to reduce unnecessary waiting time.

So to summarize my wanted behaviour would be to only wait if there already is a pending request for the same list item. Otherwise, the client should just send the request.

Edit: This is the html template for the list.

<ul>
  <li *ngFor="let item of listItems$ | ngrxPush">
     {{ item.text }} <button (click)="toggleDone(item)">{{ item.done ? 'Undo' : 'Done' }}</button>
   </li>
</ul>
Marco Wagner
  • 457
  • 4
  • 17
  • One way to deal with cancelling the current http request is via the takeUntil operator. You can work this in an Ngrx effect via an extra action. You can find an implementation of this here: https://stackoverflow.com/questions/59834002/how-cancel-angular-http-request-made-in-rxjs-effects – Mr.wiseguy Mar 08 '22 at 15:10
  • @Mr.wiseguy Sadly, if I cancel the request the backend won't notice and still persist the changes into the database. This solution only works before the request was sent. – Marco Wagner Mar 08 '22 at 15:13
  • Why not let the user play with the changes and after triggering some event, then you will make the HTTP request? with that, you avoid making unnecessary requests – Andres2142 Mar 08 '22 at 15:15
  • You could use the debounce operator (https://www.learnrxjs.io/learn-rxjs/operators/filtering/debounce) to wait x miliseconds before sending a request. That way you give the user time to figgle with the checkbox before you do anything in your code. This also saves some backend resources, since you are not sending a new request every time the user changes something (note that you need to implement this before calling the store instead of in the effect itself) – Mr.wiseguy Mar 08 '22 at 15:17
  • @Andres2142 Yes, but I struggle to find an event like this, since our design team explicitly wished for a solution that works without a dedicated save button. – Marco Wagner Mar 08 '22 at 15:19
  • @Mr.wiseguy I think using debounce just hides the problem, because when the user by chance clicks at the same time the debounce interval passed, the same problem will occur. – Marco Wagner Mar 08 '22 at 15:20
  • I would use concatMap waiting for the current request to complete but, I understand your point of editing a second separate list, it shouldn't wait... how does the template HTML look for this particular case? How are these changes been made from the componet? – Andres2142 Mar 08 '22 at 15:23
  • It does reduce the times the errors occur, but maybe this is more a backend issue? Since you cannot retract a send http request and you have no way of knowing when the backend is finished. Because of this you cannot fix it with delaying the request. This makes it very hard to fix in the front-end – Mr.wiseguy Mar 08 '22 at 15:28
  • @Andres2142 I simplified the template and added it to the question. – Marco Wagner Mar 08 '22 at 15:36
  • @Mr.wiseguy I do know when the backend is finished. It is finished when I recieve the response to the HTTP reuqest. – Marco Wagner Mar 08 '22 at 15:37

1 Answers1

1

You can achieve this by combining some RxJS operators. The main one being groupBy, where you'll split the stream into multiple groups - in your case this will be a todo item. Within each group you can use concatMap to send the requests in order.

For more info and a demo, see the following talk by Mike Ryan and Sam Julien.

https://www.youtube.com/watch?v=hsr4ArAsOL4

timdeschryver
  • 14,415
  • 1
  • 19
  • 32