Search code examples
angularngrxngrx-effectsnrwl-nx

Are there any objective benefits to using Nx's Data Persistence functions with NgRx effects?


I have an Angular app and NgRx and Nx by Nrwl. Nx provides a few "Data Persistence" functions that help you fetch, update optimistically and pessimistically, and handle navigation, but I am not sure why I would use them instead of ordinary pipes.

Here's an example on StackBlitz showing some effects with optimisticUpdate and pessimisticUpdate alongside other effects that do the same thing without them.

The question is - what advantage do these Nx functions provide?

For reference, here's the effects file from the StackBlitz:

@Injectable()
export class CarsEffects {
  updateCarOptimistic = createEffect(() =>
    this.actions.pipe(
      ofType(CarsActions.updateCarOptimistic),
      switchMap(({ selected, oldSelected, mockError }) =>
        this.carsService.updateCar(selected, mockError).pipe(
          map(() => CarsActions.updateCarOptimisticSuccess()),
          catchError(error => {
            this.store.dispatch(
              CarsActions.updateCarOptimisticFailure({ oldSelected, error })
            );
            return of(null);
          })
        )
      )
    )
  );

  updateCarOptimisticWithNx = createEffect(() =>
    this.actions.pipe(
      ofType(CarsActions.updateCarOptimisticWithNx),
      optimisticUpdate({
        run: ({ selected, mockError }) =>
          this.carsService
            .updateCar(selected, mockError)
            .pipe(map(() => CarsActions.updateCarOptimisticWithNxSuccess())),
        undoAction: ({ oldSelected }, error) =>
          CarsActions.updateCarOptimisticWithNxFailure({ oldSelected, error })
      })
    )
  );

  updateCarPessimistic = createEffect(() =>
    this.actions.pipe(
      ofType(CarsActions.updateCarPessimistic),
      switchMap(({ selected, mockError }) =>
        this.carsService.updateCar(selected, mockError).pipe(
          map(() => CarsActions.updateCarPessimisticSuccess({ selected })),
          catchError(error =>
            of(CarsActions.updateCarPessimisticFailure({ error }))
          )
        )
      )
    )
  );

  updateCarPessimisticWithNx = createEffect(() =>
    this.actions.pipe(
      ofType(CarsActions.updateCarPessimisticWithNx),
      pessimisticUpdate({
        run: ({ selected, mockError }) =>
          this.carsService
            .updateCar(selected, mockError)
            .pipe(
              map(() =>
                CarsActions.updateCarPessimisticWithNxSuccess({ selected })
              )
            ),
        onError: (_, error) =>
          CarsActions.updateCarPessimisticWithNxFailure({ error })
      })
    )
  );

  constructor(
    private actions: Actions,
    private store: Store,
    private carsService: CarsService
  ) {}
}

NOTE: I don't mean for this to be a question about matters of opinion on preferable syntax; just wanting to make my code as performant, secure, and clean as possible, and would like to use these functions if they are doing something behind the scenes I am unaware of.


Solution

  • Yes, if you provide an id selector function. Check out the examples here. In my own testing, if you dispatch an action multiple times, every request will go through unless you use the id selector. Once you add that in, fetch will cancel any existing network requests if a new action comes through with the same ID.

    This functionality can be obtained with a simple switchMap operator, but the fetch utility has another advantage that switchMap doesn't provide: dispatching an action with a different ID while a network request is in progress will not cancel the current request.

    Consider the following example:

    this.store.dispatch(getUser('AAA'));
    this.store.dispatch(getUser('BBB'));
    this.store.dispatch(getUser('AAA'));
    

    With switchMap, only the last request would go through, so you would never successfully fetch user BBB. With fetch, however, the last two requests would go through.