Saurabh Kumar
Saurabh Kumar

Reputation: 16661

Reseting the state on logout

In my cart service, I want to reset the state tree for example when a user logs out. How can I achieve this? Stackblitz playground stackblitz

export interface StateTree {
  store: CartItem[];
  cart: CartItem[];
  tot: Totals,
  checkout: boolean;
};

My variables.

 private stateTree$ = new BehaviorSubject<StateTree>(null);
 private checkoutTrigger$ = new BehaviorSubject<boolean>(false);
 private cartAdd$ = new Subject<CartItem>();
 private cartRemove$ = new Subject<CartItem>();

My sate

state$: Observable<StateTree> = this.stateTree$.pipe(
 switchMap(() => this.getItems().pipe(
  combineLatest([this.cart$, this.total$, this.checkoutTrigger$]),
  debounceTime(0),
 )),
 map(([store, cart, tot, checkout]: any) => ({ store, cart, tot, checkout })),
 tap(state => {
  if (state.checkout) {
    console.log('checkout', state);
  }
 }),
 // make sure we share to the world! or just the entire app
 shareReplay(1)
 );

Upvotes: 2

Views: 661

Answers (2)

Jonathan Stellwag
Jonathan Stellwag

Reputation: 4267

In your stackblitz I have added you a logout button on your page that is only a private logout$ subject in your service.

Refactored private get cart$

  private get cart$(): Observable<CartItem[]> {
    const addOperation = (item) => (acc) => [...acc, item];
    const removeOperation = (item) => (acc) => [...acc.filter(i => i.uuid !== item.uuid)];
    const resetOperation = () => (acc) => [];

    return merge(
      this.cartAdd$.pipe(map(addOperation)),
      this.cartRemove$.pipe(map(removeOperation)),
      this.logout$.pipe(map(resetOperation))
    ).pipe(
      scan((acc, fn) => fn(acc), []),
      startWith([])
    );
  }

The state mutation is applied to the obersvables, before the scan. This mechanism lets you avoid to filter for specific actions in the scan itself. Like you did before by asking for if (item.remove) {...} else {...}. Now you can easily create more state mutations and add specific functions to it, without enlarging your interfaces.

FYI: I also had a quick small refactoring in your private get total$. No need to adapt it. I just thought it was nicer this way.

Upvotes: 1

seangwright
seangwright

Reputation: 18205

Issues to address

I think there are 2 separate issues to address here.

  1. What does it mean to 'reset' the state tree? What is the default state?
  2. How does logging out happen and how do we translate that event to a change in 'state' of the application?

What does it mean to reset?

A common way to solve 1 is to define functions that produce a default/initial state:

export function createInitialStateTree(): StateTree {
  return {
    store: [];
    cart: [];
    tot: {
      subTot: 0;
      tax: 0;
      grandTot: 0;
    },
    checkout: false;
  }
}

Then when you want to reset the application state, assign the result of that function into the place where app state is stored (BehaviorSubject<StateTree>):

this.stateTree$.next(createInitialStateTree());

How to perform a reset on logout?

As for 2, we don't want to expose the application state directly to consumers to change in any way they want, so keeping stateTree$ private within a service is a good first step.

But we do want to allow other parts of the application to change the state in very specific ways that we define.

This means creating a method on the service that either accepts parameters that are used to calculate the state change, or simply a method that takes no parameters that results in a very specific side-effect of a state change.

I think the 2nd of these options makes the most sense for a 'sign-out' here.

Add a method to your ShoppingCartService to expose the 'reset' state change:

resetCart() {
  this.stateTree$.next(createInitialStateTree());
}

And call this method after the user has signed out:

// in some other part of the app

signOut() {
  this.http.post('/api/signout').pipe(
    tap(() => this.shoppingCartService.resetCart())
  )
  .subscribe();
}

Upvotes: 0

Related Questions