9

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.

  • https://ngrx.io/guide/store/selectors – penleychan Oct 28 '19 at 19:30
  • What do you import to have access to the selectors that are already present when using ngrx/data? –  Oct 28 '19 at 19:39
  • 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 Oct 28 '19 at 20:24
  • It's not documented, when you import the service that extends `EntityCollectionServiceBase` 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. –  Oct 29 '19 at 15:52
  • https://ngrx.io/guide/data/entity-collection-service is the closest thing I could find in the documentation. –  Oct 29 '19 at 16:17

3 Answers3

7

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

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]
  }))
);
timdeschryver
  • 14,415
  • 1
  • 19
  • 32
-3

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 :)

  • 4
    Well that's the million dollar question, how do you extend the state with the ngrx/data generated entities? ngrx/data isn't ngrx/entity, it might use ngrx/entity but is really a lot more, since it has a cache. When I check the store tools those entities are present in the state, just not defined in my state. –  Oct 28 '19 at 19:01