2

My question is a continuation of this excellent question and answer concerning the shape of paginated data in a redux store. I am using ngrx/store in an angular 2 app.

{
  entities: {
    users: {
      1: { id: 1, name: 'Dan' },
      42: { id: 42, name: 'Mary' }
    }
  },
  visibleUsers: {
    ids: [1, 42],
    isFetching: false,
    offset: 0
  }
}

Based on the above shape I believe if the offset (or page, sort, etc.) from an incoming request payload changed then the visible users would change as well as the user entities by calling the DB. I have some actions and reducer functions to handle this and it works as expected. If the offset remains the same and the user is returning to the page the way they left it then the user entities should be returned by the store not the DB.

Where I am struggling is where to put that logic and which rxjs operators to use (still learning this).

I think the correct place is an effect. Here is what I have now in my angular2 app (I am injecting Actions, Store, and my UserService) that pulls new data every time the page is loaded.

@Effect loadUsers$ = this.actions$
     .ofType('LOAD_USERS')
     .switchMap(() => this.userService.query()
         .map((results) => {
             return new LoadUsersSuccessAction(results);
         }))
     .catch(() => Observable.of(new LoadUsersFailureAction()));

My best idea is something like this:

@Effect loadUsers$ = this.actions$
     .ofType('LOAD_USERS')
     .withLatestFrom(this.store.select(state => state.visibleUsers.offset))
     .switchMap(([action, state]) => {
         //something that looks like this??
         //this syntax is wrong and I can't figure out how to access the action payload
         state.offset === payload.offset 
            ? this.store.select(state => state.entities.users) 
            : this.userService.query()
         }
         .map((results) => {
             return new LoadUsersSuccessAction(results);
         }))
     .catch(() => Observable.of(new LoadUsersFailureAction()));

Not sure how to make this work. Thanks ahead.

Community
  • 1
  • 1
trevorc
  • 3,023
  • 4
  • 31
  • 49
  • 1
    Not related to your question, but you should put your `catch` inside the `switchMap`. The observable returned by `catch` will complete and - outside the `switchMap` - that will see the effect's observable complete and the effect will stop working if an error occurs. – cartant Apr 07 '17 at 00:29
  • It actually is. The posted code is formatted wrong. Thanks for the heads up tho. Learned that one the other day. – trevorc Apr 07 '17 at 00:35
  • 1
    If you want to create "virtual view" with only some data from the store, take a look into selectors : http://redux.js.org/docs/recipes/ComputingDerivedData.html – maxime1992 Apr 07 '17 at 06:31

1 Answers1

5

I don't like answering my own questions but it took me quite a while to find the answer to this. I won't accept this answer as I am not sure this is the best way to go about things (still learning the ins/outs). It does however, work perfectly.

I found the correct syntax in a github gist. This code does not match my example but it clearly demonstrates "2 options for conditional ngrx effects" by either returning a store or api observable using some sort of condition.

Hope this helps someone.

Here it is:

  @Effect()
  selectAndLoadStore$: Observable<Action> = this.actions$
    .ofType(storeActions.SELECT_AND_LOAD_STORE)
    .withLatestFrom(this.store.select(ngrx.storeState))
    .map(([action, storeState]) => [action.payload, storeState])
    .switchMap(([storeName, storeState]) => {
      const existsInStore = Boolean(storeState.urlNameMap[storeName]);
      return Observable.if(
        () => existsInStore,
        Observable.of(new storeActions.SetSelectedStore(storeName)),
        this.storeService.getByUrlName(storeName)
          .map(store => new storeActions.LoadSelectedStoreSuccess(store))
      );
    });

  @Effect()
  selectAndLoadStore$: Observable<Action> = this.actions$
    .ofType(storeActions.SELECT_AND_LOAD_STORE)
    .withLatestFrom(this.store.select(ngrx.storeState))
    .map(([action, storeState]) => [action.payload, storeState])
    .switchMap(([storeName, storeState]) => {
      const existsInStore = Boolean(storeState.urlNameMap[storeName]);
      let obs;
      if (existsInStore) {
        obs = Observable.of(new storeActions.SetSelectedStore(storeName));
      } else {
        obs = this.storeService.getByUrlName(storeName)
          .map(store => new storeActions.LoadSelectedStoreSuccess(store));
      }
      return obs;
    });
trevorc
  • 3,023
  • 4
  • 31
  • 49