1

I have a large project that's slowly migrating to the new ngrx syntax. We have many selectors that are plain functions like this:

export function appDetailsSelector(state$: Observable<AppState>): Observable<AppDetails> {
    return state$.pipe(map((appState: AppState) => appState.appDetails));
}

New selectors are written using createSelector:

export const selectUserConfig: MemoizedSelector<AppState, UserConfig> = createSelector(
  selectUserData,
  (userData: UserData) => userData.config,
);

Both of these use the same state shape, but how do I combine to create a new selector combining both of these with createSelector?

I know I can use piping to do this in the component code:

combineLatest([
  this.store.pipe(appDetailsSelector),
  this.store.select(selectUserConfig),
], (appDetails, userConfig) => ...projector fn code...);

But this isn't reusable and I need it done with createSelector to avoid repeating this in multiple components. If appDetailsSelector was created with createSelector it would look like this:

export const getUserBookmarks = createSelector(
  appDetailsSelector,
  selectUserConfig,
  (appDetails: AppDetails, userConfig: UserConfig) => appDetails.useBookmarks ? userConfig.bookmarks : []
);

But this Argument of type 'MemoizedSelector<AppState, UserConfig, DefaultProjectorFn<UserConfig>>' is not assignable to parameter of type 'SelectorWithProps<Observable<AppState>, unknown, UserConfig>'.   Types of parameters 'state' and 'state' are incompatible.     Type 'Observable<AppState>' is missing the following properties from type 'AppState'...

I can see that the old style selectors take Observable<AppState> and the new ones assume AppState; how to bridge this gap and combine these two selectors?

Owen Kelvin
  • 14,054
  • 10
  • 41
  • 74
charmeleon
  • 2,693
  • 1
  • 20
  • 35

2 Answers2

1

You can use typecasting

export const getUserBookmarks = createSelector(
  appDetailsSelector as MemoizedSelector<AppState, UserConfig, DefaultProjectorFn<UserConfig>>,
  selectUserConfig as MemoizedSelector<AppState, UserConfig, DefaultProjectorFn<UserConfig>>,
  (appDetails: AppDetails, userConfig: UserConfig) => appDetails.useBookmarks ? userConfig.bookmarks : []
);

Typecasting will set the type to the same type

Owen Kelvin
  • 14,054
  • 10
  • 41
  • 74
0

There's no way to use Observable selectors with the new createSelector syntax. Observable selectors operate on an observable, while function selectors operate on the data contained within the observable directly; there's no out-of-the-box way to make these work together.

Note that an observable selector is actually a function selector in disguise; if you need to maintain your classic selectors, just move the stateful logic out into a function selector and use the correct selector per-context.

export const appDetailsSelector = (state: AppState) => state.appDetails;

export const appDetailsObservableSelector = (state$: Observable<AppState>) =>
  state$.pipe(map(appDetailsSelector));
superhawk610
  • 2,457
  • 2
  • 18
  • 27