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!
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(/* .. */);
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));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With