9
votes

I have a state and I'd like to create selectors from ngrx/data entities.

import {
  Action,
  ActionReducer,
  ActionReducerMap,
  createFeatureSelector,
  createSelector,
  MetaReducer
} from '@ngrx/store';
import {environment} from '../../environments/environment';
import * as fromRouter from '@ngrx/router-store';
import * as fromDrawer from './drawer';
import {InjectionToken} from '@angular/core';
import {NavigationItem} from '../models/navigation-item';

export interface State {
  router: fromRouter.RouterReducerState<any>;
  drawerNavigationItems: fromDrawer.State;
}

export const ROOT_REDUCERS = new InjectionToken<ActionReducerMap<State, Action>>('Root reducers token', {factory: () => ({
    router: fromRouter.routerReducer,
    drawerNavigationItems: fromDrawer.reducer,
  }),
});

export const metaReducers: MetaReducer<State>[] = !environment.production ? [] : [];

export const selectRouter = createFeatureSelector<
  State,
  fromRouter.RouterReducerState<any>
  >('router');

const {
  selectQueryParams,    // select the current route query params
  selectQueryParam,     // factory function to select a query param
  selectRouteParams,    // select the current route params
  selectRouteParam,     // factory function to select a route param
  selectRouteData,      // select the current route data
  selectUrl,            // select the current url
} = fromRouter.getSelectors(selectRouter);

export const selectRouteId = selectRouteParam('id');
export const selectStatus = selectQueryParam('status');


// Drawer

export const selectDrawerNavigationItems = (state: State) => state.drawerNavigationItems.items as NavigationItem[];

How do I use the pre-defined selectors or write my own with entities or services that came from ngrx/data?

As an example I'd like to create a selector that selects all "Community" entities and then, step 2, select 1 by selectRouteId. If you imagine a route /communities/:id, selectRouteId returns the Id, and now I'd like the data from the CommunityService and use the selector created or somehow imported and used in step 1 and return a result, 1 Community with the selectRouteId's id, so I can later do something like this.store.dispatch(selectCommunityByCurrentRouteId);

This question is specific to @ngrx/data.

3
What do you import to have access to the selectors that are already present when using ngrx/data?user12207064
Hmm, I don't see anywhere in the docs that says ngrx/data have selectors created. Can you point me to the documentation where ngrx/data provides selectors OOTB? AFAIK ngrx/entity provides that.penleychan
It's not documented, when you import the service that extends EntityCollectionServiceBase<T> you have access to the defined selectors, in the component, but not in the reducer. When you try to import the extended class all you have access to is the .prototype. You can import the EntityCollectionServiceFactory and the docs say there is a .create method and yes, but when I try to call it, there's some error, since it isn't static, but you can't create an instance without passing AnotherVeryLongNamedClass in the constructor.user12207064
ngrx.io/guide/data/entity-collection-service is the closest thing I could find in the documentation.user12207064

3 Answers

7
votes

a supplementary answer, to concretely answer my own question, here's what the reducers/index.ts looks like now

import {
  Action,
  ActionReducer,
  ActionReducerMap,
  createFeatureSelector,
  createSelector,
  MetaReducer
} from '@ngrx/store';
import {environment} from '../../environments/environment';
import * as fromRouter from '@ngrx/router-store';
import * as fromDrawer from './drawer';
import {InjectionToken} from '@angular/core';
import {NavigationItem} from '../models/navigation-item';
import {EntitySelectorsFactory} from '@ngrx/data';
import {Community} from '../models/community';

export interface State {
  router: fromRouter.RouterReducerState<any>;
  drawerNavigationItems: fromDrawer.State;
}

export const ROOT_REDUCERS = new InjectionToken<ActionReducerMap<State, Action>>('Root reducers token', {factory: () => ({
    router: fromRouter.routerReducer,
    drawerNavigationItems: fromDrawer.reducer,
  }),
});

export const metaReducers: MetaReducer<State>[] = !environment.production ? [] : [];

export const selectRouter = createFeatureSelector<
  State,
  fromRouter.RouterReducerState<any>
  >('router');

const {
  selectQueryParams,    // select the current route query params
  selectQueryParam,     // factory function to select a query param
  selectRouteParams,    // select the current route params
  selectRouteParam,     // factory function to select a route param
  selectRouteData,      // select the current route data
  selectUrl,            // select the current url
} = fromRouter.getSelectors(selectRouter);

export const selectRouteId = selectRouteParam('id');
// export const selectStatus = selectQueryParam('status');

// Data

export const communitySelectors = new EntitySelectorsFactory().create<Community>('Community');

export const selectCommunityByRouteId = createSelector(
  selectRouteId,
  communitySelectors.selectEntities,
  (id, communities) => communities.find(c => c.id === id)
);


// Drawer

export const selectDrawerNavigationItems = (state: State) => state.drawerNavigationItems.items as NavigationItem[];

You create a selector for the Community model with

export const communitySelectors = new EntitySelectorsFactory().create<Community>('Community');

and then you combine those two and return 1 Community by the route id.

export const selectCommunityByRouteId = createSelector(
  selectRouteId,
  communitySelectors.selectEntities,
  (id, communities) => communities.find(c => c.id === id)
);

really simple, you pick the input streams, provide a projection function and return the result.

Later, in the component

export class OneCommunityComponent implements OnInit {
  community$: Observable<Community>;
  constructor(
    private store: Store<State>,
  ) {
  this.community$ = this.store.select(selectCommunityByRouteId);
  }
}
6
votes

See https://github.com/peterbsmith2/platform/blob/b2f17bfcc987bf63d10dd207263c0ca2a2e44373/projects/ngrx.io/content/guide/data/extension-points.md#custom-selectors.

/* src/app/reducers/index.ts */
import * as fromCat from './cat.reducer';
import { Owner } from '~/app/models'

export const ownerSelectors = new EntitySelectorsFactory().create<Owner>('Owner');

export interface State {
  cat: fromCat.State;
}

export const reducers: ActionReducerMap<State> = {
  cat: fromCat.reducer
};

export const selectCatState = (state: State) => state.cat;

export const {
  selectAll: selectAllCats
} = fromCat.adapter.getSelectors(selectCatState);

export const selectedCatsWithOwners = createSelector(
  selectAllCats,
  ownerSelectors.selectEntities,
  (cats, ownerEntities) => cats.map(c => ({
    ...c,
    owner: ownerEntities[c.owner]
  }))
);
-3
votes

The best solution in your situation it's adapters is very nice. You can look just here : https://ngrx.io/guide/entity/adapter

You can remove, add, update every object in your store with that and very easily. You just need to extend your state :)