Cleaner way of subscribing to the actions stream with NGXS
Asked Answered
L

2

7

So I'm usually having a method that dispatches an action on click:

create() {
  this.store.dispatch(new Create(this.form.value)); 
}

this code triggers the following scenario and dispatches a CreateSuccess or CreateFailed depending of if the request failed or not

@Action(Create)
create({ dispatch }: StateContext<StateInterface>, { entity}: Create) {
  return this.http.post('mybackend', entity).pipe(
    tap<EntityType>(resp => dispatch(new CreateSuccess(resp))),
    catchError(error => dispatch(new CreateFailed(error)))
  ); 
}

Now inside the Component where I called the create() I'm listening for those two actions.

this.actions$.pipe(
  ofActionSuccessful(CreateSuccess, UpdateSuccess),
  takeUntil(componentDestroyed(this)) // <--
).subscribe(action => doStuff());

This all works flawlessly, the only thing that bothers me is that every time I use this, I have to add the takeUntil() part so the subscription is cancelled when the component is destroyed.

I know this is probably not a real issue for everyone, but I'd like to know if there is a cleaner way to do this.

I've thought about a custom RxJS operator that could handle this, but maybe there are other options, or (something I haven't found anything about), is there a way that NGXS does the unsubscribing itself?

Lest answered 29/10, 2018 at 14:39 Comment(3)
Any reason you are subscribing to the actions directly? Typically you should be able to just subscribe to the state and then use async pipe in your template to do all the rendering of the state. Async pipe will take care of all unsubscribing.Milkfish
@Milkfish In this specific example I'm using a dialog and I want to close the dialog after the action succeeded, so sadly not possible to use that.Lest
What I typically do is keep a variable on the state that indicates whether dialog needs to be open or not (ex: isDialogOpen). You can then bind the opening/closing of the modal/dialog to that variable. That way, setting the variable to false after action is successful should close the modal!Milkfish
S
5

In NGXS the dispatch method returns an observable that will complete once the action handling is complete (no unsubscribe necessary). So in your example your Create action will complete when the handlers of the action have completed. If a handler returns an observable (like you have done) then the completion is linked to the completion of that observable. If you were to use a map as opposed to a tap for the dispatching of the CreateSuccess action then the originating observable will wait for that 'child' observable to complete first. I would structure the handler like this to make the Create action complete only once the CreateSuccess has completed:

@Action(Create)
create({ dispatch }: StateContext<StateInterface>, { entity}: Create) {
  return this.http.post('mybackend', entity).pipe(
    map<EntityType>(resp => dispatch(new CreateSuccess(resp))),
    catchError(error => dispatch(new CreateFailed(error)))
  ); 
}

Then in your component you can do this:

create() {
  this.store.dispatch(new Create(this.form.value))
    .subscribe(action => doStuff()); 
}

The observable would complete when the dispatched action's handling (and 'child' action handling) is completed. No unsubscription necessary.

Satisfy answered 31/10, 2018 at 19:5 Comment(2)
Thanks! This does sound like a possible variant, but is there any way to know if the request succeeded or failed? And to get the return value of the action? I think there was a limitation that the return value cannot be determined, right?Lest
The subscribe method does allow you to provide callbacks for next, error, and completed. The value from the action response is unfortunately not available because it is a multicast dispatch. I would assume that the value would land up in your state, so you could use store.selectSnapshot to get itSatisfy
S
0

NGXS does not provide any special features to unsubscribe based on Angular component's lifecycle.

Generally, unsubscribing when a component is destroyed make sense. However, there are scenarios where you want more specific control when to subscribe/unsubscribe.

So, you will have to manage it in every Angular component's appropriate lifecycle functions.

Stealing answered 30/10, 2018 at 5:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.