0
votes

How to handle a filter functionality for my store ?

Currently it is fetching data from an external source using a service via effects.

Once we fetch the data it will be available inside my store .

So I want to filter out the entities in my store (local ngrx store & not the backed database) based on some filter criteria.

this is the interface of the item.

export interface item {
    id: string;
    name: string;
    price: number;
    location: string;
    category: string;
    ...
}
// component.ts

this.store.dispatch(loadItems())

this.Items$ = this.store.pipe(select(selectItems))

//actions.ts

export const loadItems = createAction(
    '[Item Items Component] Load Items',
  );
  export const loadItemsSuccess = createAction(
    '[Item Items/API] Load Items Success',
    props<{ Items: Item[] }>()
  );
  export const loadItemsFailure = createAction(
    '[Item Items/API] Load Items Failure',
    props<{ error: any }>()
  );


// effects.ts

loadItems$ = createEffect(() =>
this.actions$.pipe(
  ofType(ItemActions.loadItems),
  mergeMap(action =>
    this.defaultService.getContentModeration().pipe(
      map(Items => ItemActions.loadItemsSuccess({ Items })),
      catchError(error =>
        of(ItemActions.loadItemsFailure({ error }))
      )
    )
  )
)
)



// selector.ts

export const selectItemState = createFeatureSelector<ItemState>(
  ItemsFeatureKey
);

export const selectItems = createSelector(selectItemState, selectAll);

// reducer.ts

export const ItemsFeatureKey = 'Items';

export interface ItemState extends EntityState<Item> {
  // additional entities state properties
  error: any;
}

export const adapter: EntityAdapter<Item> = createEntityAdapter<Item>({
  sortComparer: (c1: Item, c2: Item) => {
    const compare = c1.timestamp - c2.timestamp;
    return compare > 0 ? 11 : compare < 0 ? 1 : 0
  },
  selectId: Item => Item.contentId,
});

export const initialState: ItemState = adapter.getInitialState({
  // additional entity state properties
  error: undefined
});

export const reducer = createReducer(
  initialState,
  on(ItemActions.loadItemsSuccess,
    (state, action) => adapter.setAll(action.Items, state)
  ),
  on(ItemActions.loadItemsFailure,
    (state, action) => {
      return {
        ...state,
        error: action.error
      }
    }
  ),

  export const {
    selectIds,
    selectEntities,
    selectAll,
    selectTotal,
  } = adapter.getSelectors();

Currently I am using a filter pipe. but that is not feasible. It is slowing down my app. I want to leverage the potential of a store which handles the state

So I want to filter the items(entities) in the store based on either by name, price, category or by combined name, price, category. Just like regular filters we see on sites like amazon, flipkart etc...

All solutions out there are old I think(some of them uses switch statements, but recent ngrx selectors on() method & actions are also different from previous versions).

can someone please help?

If you don't have time to answer please tell whether this is a dead end or not. So that I may continue using pipes and stop searching further.

1

1 Answers

0
votes

If you need multiple streams of filtered data from a single store (aka derived state) use selectors with props:


// selectors
export const selectFilteredItems = createSelector(
  selectItemState, 
  selectAll, 
  (items, props) => items.filter(entity => entity.name === props.name)
);
export const selectFilteredByTypeItems = () => {}


//component
this.filteredByNameItems$ = this.store.select(selectFilteredItems, {name: 'name1'});
this.filteredByTypeItems$ = this.store.select(selectFilteredByTypeItems, {type: 'type1'});

if you want to set the filter from the UI, you should store this filter in a state and use this filter in selectors:

// reducers
const initialState = {
  filter: {name: '', type: ''}
}

// selectors
export const selectFilter = createSelector(
  selectItemState,
  state => state.filter
);

export const selectFilteredItems = createSelector(
  selectItems,
  selectFilter,
  ([allItems, uiFilter]) => allItems.filter(item => item.name === uiFilter.name)
); // note the combination of two selectors: for all items and for the filter

don't forget to add actions and reducers to manipulate UI filter