Search code examples
angulartypescriptrxjsngrxngrx-store

NGRX filter opererator in selector throws source.lift is not a function exception


Using NGRX and Angular 13 (just for clarity), I have a store with the following state:

export const initialState: SomethingState = {
  data: null,
  succeeded: false,
  message: "",
  errors: null,
};

And it has a feature selector as such:

export const selectSometingFeatureSelector = createFeatureSelector<SomethingState>("somthing");

Each time an API call completes the 'succeeded' value on the state is updated to true (provided no errors happened).

Then in each component where I use the 'selectSometingFeatureSelector' I always check to make sure the call has succeeded (and sometimes use a take(1) afterwards to auto unsubscribe). For example:

this.store.select(selectSometingFeatureSelector)
.pipe(
      filter((state) => state.succeeded),
      take(1),
      map((state) => {
        return state.data.items;
      })
);

This is all fine (I think) but I find myself repeating the code a lot as all our states have 'succeeded' on them.

I wanted to move this to a selector so at first I wrote the below but it didn't work as the observable still fired before succeeded:

export const selectSomething = () =>
  createSelector(
    selectSomethingFeatureSelector,
    (state: SometingState) => {
      if(state.suceeded) {
      return return state.data.items;
    }
  }
);

Looking at the code I'm not that surprised it didn't work (but it was worth a shot). Then after more research it seemed like this was the proper approach:

export const selectSomething = () =>
  createSelector(
    selectSomethingFeatureSelector, 
    filter((state: SomthingState) => state.suceeded === true)
    (state: SometingState) => {
      return return state.data.items;    
  }
);

Whilst the above seems to make perfect sense to me and I get no complaints in VS code I get the following error in the console:

TypeError: source.lift is not a function at filterOperatorFunction

So my question is in 2 parts:

  1. Am I right to be moving this to a selector or should I keep it in the component?
  2. If a selector is the right way to go then what have I done wrong?

Solution

  • The rxjs filter operator is not meant to be used in `createSelector() but you can do a pipeable operator :

    export const selectSomething = pipe(
      select(selectSomethingFeatureSelector),
      filter(val => state.suceeded === true)
      map((state: SometingState) => state.data.items)
    );
    

    Which can be called like this :

    store.pipe(selectSomething).subscribe(/* .. */);