angularsignalsngrxngrx-signal-store

Accessing different SignalStore in Ng/Rx withMethods


I'm used to traditional Ng/Rx and am now delving into Signals and the Ng/Rx SignalStore.

I'm aware that with SignalStore, we can supply the withMethods. We can then invoke those methods on our store from the components where we've injected the store.

Let's consider that I have a books store, and a user store. The user store stores the current logged in user. It will basically only change if the user logs in/out. The book store stores a list of books, and has a method to load the books for the current user.

Is the below code how one is expected to set this up? In traditional Ng/Rx we'd have an effect that react to the action and subsequently gets the latest value from the user state, performs the request, and then dispatches another action. Having the userStore mentioned in the bookStore feels like a code smell to me.

const BooksStore = signalStore(
  withState(initialState),
  withMethods((store) => (
    const httpClient = inject(HttpClient);
    const userStore = inject(UserStore);    

    return loadBooksForLoggedInUser(): void {
      httpClient.get<TalkData>(`/books?userId=${userStore.id()}`)
      // use the result and patch state etc. etc.
    }
  ))

P.s. The code above may not be perfect, as I simply wrote it up in stackoverflow to get my question across. At this time I don't have any actual code to share because my question is hypothetical. I have searched, but I can't find anyone who's been in this situation, but that feels odd...because it feels like it should be fairly common.


Solution

  • I don't think that using one store in the withMethods of the other store is a code smell, in fact, I think it is the intended architecture for cases where you have 2 stores with different scopes. (in your case it is clear that the user store has a longer or equal lifespan than the books store).

    I agree that you should use an effect so that whenever the user id changes, you will reload the books for the current user. I also think you should use an rxMethod because the api is asyncronous.

    withMethods(store => {
         const http = inject(HttpClient);
    
       return {
         loadBooksForCurrentUser: rxMethod<number>(id$ => id$.pipe(
            tap(/* empty the talk data, set "busy" to true... */),  
            switchMap(id => httpClient.get<TalkData>(/* the url*/)), 
            tap(talkData => // use the result and patch state //)
         ))
       }
    
    }), 
    withHooks(store => {
       const userStore = inject(UserStore);
       return {
          onInit() {
              // note - we pass the id signal by reference!!!. 
              // rxMethod subscribes to it so it is called again 
              // whenever it changes
              store.loadBooksForCurrentUser(userStore.id); 
          }
       }
    })