ngrx selectors with filters

18,842

Solution 1

For that kind of scenario where I want to reuse part of a stream I create a function that accepts the store as a parameter and does some operations on it. The following is an example:

const selectMyState = (state) => state.myState;
export const getData = createSelector(selectMyState, state => state.data)
export const getDataWhenLoaded = (store) => {
    return store.select(getData)
        .filter(myState => myState.isLoaded);
};
...
getDataWhenLoaded(this.store).subscribe(...);

As far as using the last parameter of the createSelector function, you can depending on your need. In the documentation it mentions that the same state passed to the memoized selector created by createSelector will not recompute the projection. It will return the same value when invoked. I couldn't find documentation on this but through testing I noticed that if the projection results in the same value as the previous value then it will not be emitted if the subscription already received the previous value. Similar behavior to the rxjs operator distinctUntilChanged. I haven't had time to dig through their source code to understand where/how/why.

So in some cases you can put a filter in the projection parameter (last) for createSelector. Here is an example:

export const getActive = createSelector(selectMyData, state => state.data.filter(x => x.isActive));

Solution 2

The example is from ngrx.io

Assuming you have the selectValues selector and you want to filter the values making the code reusable.

import { select } from '@ngrx/store';
import { pipe } from 'rxjs';
import { filter } from 'rxjs/operators';

export const selectFilteredValues = pipe(
 select(selectValues),
 filter(val => val !== undefined)
);

store.pipe(selectFilteredValues).subscribe(/* .. */);
Share:
18,842

Related videos on Youtube

David
Author by

David

Engineering Manager with 15+ years of experience in the field. Avid learner. Servant leader. A true believer in the importance of feedback and communication as a means to achieve success in a team.

Updated on September 15, 2022

Comments

  • David
    David over 1 year

    I'm using @ngrx in an Angular application and I'd like to abstract one of the recurrent patterns that I already have.

    My state is typically is composed of many states like this:

    myState: {
      data: any
      isLoading: boolean
      isLoaded: boolean
    }
    

    so what I end up doing in the code is subscribing to it but I only want to be notified if the state has been loaded.

    this.store
      .select(state => state.myState)
      .filter(myState => myState.isLoaded)
      .map(myState => myState.data)
      .do(data => do_whatever_I_need_to_do)
      .subscribe();
    

    this works for me, but I was wondering if I could simplify the first 3 operators as they are quite recurrent.

    By using selectors I could create something like this

    const selectMyState = (state) => state.myState;
    
    export const getData = createSelector(selectMyState, state => state.data)
    

    and then use it like this

    this.store
      .select(getData)
      .do(data => do_whatever_I_need_to_do)
      .subscribe();
    

    but then I'm missing the filter part, so I might get stream events from it even though my data has not yet been loaded.

    Is there any way to include that missing filter into the observable? I know the createSelector has a function as param, so that I can do some code in there, but I'm not sure how I would use that to filter not data, but the event itself.

    Any ideas? Thanks!

  • David
    David over 6 years
    thanks @bygrace I'll have a look at adding this to the last parameter as you mention, see if it works
  • David
    David over 6 years
    That last bit didn't work, but the first solution about creating a function injecting the store does. It felt a bit hacky to me at first, but code feels clean enough and gives me what I wanted, so I will accept the answer.
  • bygrace
    bygrace over 6 years
    @David yeah, the last part wont work for your specific situation. I would probably only use it for lists at best but I really just use the first solution to avoid being tricky and relying on possibly undocumented features.
  • MrD
    MrD about 5 years
    Exactly what I need. Thx!
  • Skorunka František
    Skorunka František about 4 years
    Or store.select(selectors.getData).pipe(filter((x) => !!x)); where !!x is your condition. Don't forget to import { filter } from 'rxjs/operators';