Reputation: 16661
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
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
Reputation: 18205
I think there are 2 separate issues to address here.
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());
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