Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ngrx selectors with filters

Tags:

angular

ngrx

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!

like image 348
David Avatar asked Feb 07 '18 07:02

David


2 Answers

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(/* .. */);
like image 169
Max Epelbaum Avatar answered Oct 12 '22 18:10

Max Epelbaum


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));
like image 6
bygrace Avatar answered Oct 12 '22 20:10

bygrace