Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular NGRX: multiple Entities in one EntityAdapter possible?

Tags:

angular

ngrx

Recently NGRX/Entities have been introduced:

https://medium.com/ngrx/introducing-ngrx-entity-598176456e15 https://github.com/ngrx/platform/tree/master/example-app

And since they are made in a way that an adapter handles a (read: one, single) map-datastructure and on initialization then gets the rest of the reducer state, I was wondering...

Is it possible to hold multiple Entities in one reducer/adapter? The interface says no but maybe there is a hack or it is planned for the future? What if I already have multiple maps in one reducer? Am I forced to split it up or avoid the Entities feature?

The answer below is valid. An alternative approach for (in this example lazily loaded, but not necessarily) modules is to combine reducers with ActionReducerMap:

in your lazy.module.ts:

export interface LazyState {
  lazyAState: LazyAState;
  lazyBState: LazyBState;
}

export const lazyReducers: ActionReducerMap<LazyState> = {
  lazyA: lazyAReducer,
  lazyB: lazyBReducer
};

export interface AppState extends forRoot.AppState {
  lazy: LazyState;
}

@NgModule({
  imports: [
    LazyRoutingModule,
    StoreModule.forFeature('lazy', lazyReducers),
    EffectsModule.forFeature([LazyEffects])
  ]
})
export class LazyModule {
  static forRoot() {
    return {
      ngModule: LazyModule,
      providers: [],
    };
  }
}

And in lazy.selectors.ts (you import the adapters from the reducer-file):

export const getLazyState = createFeatureSelector<LazyState>('lazy');

const getLazyAState = createSelector(getLazyState, (state: LazyState) => state.lazyAState);
const getLazyBState = createSelector(getLazyState, (state: LazyState) => state.lazyBState);

const {selectEntities: lazyASelectEntities, selectAll: lazyASelectAll} = LAZY_A_ADAPTER.getSelectors();

export const lazyADictionary = createSelector(getLazyAState, lazyASelectEntities);
export const lazyAArray = createSelector(getLazyAState, lazyASelectAll);
export const lazyASomeOtherAttributeNotFromAdapter = createSelector(getLazyAState, (state: LazyAState) => state.ids as string[]);

const {selectEntities: lazyBSelectEntities, selectAll: lazyBSelectAll} = LAZY_B_ADAPTER.getSelectors();
// same as for lazy-A, you can also combine selectors if you want
like image 329
Phil Avatar asked Feb 05 '18 11:02

Phil


People also ask

What are entities in NgRx?

NgRx Entity is a small library that helps us to keep our entities in this ideal Entity state format (array of ids plus map of entities). This library is designed to be used in conjunction with NgRx Store and is really a key part of the NgRx ecosystem.

What is an entity adapter?

An entity adapter is a plain JS object (not a class) containing the generated reducer functions, the original provided selectId and sortComparer callbacks, a method to generate an initial "entity state" value, and functions to generate a set of globalized and non-globalized memoized selector functions for this entity ...

What is the use of ngrx entity adapter?

Entity Adapter NgRx has EntityAdapterinterface that provides many collection methods for managing the entity state. It is instantiated using NgRx createEntityAdaptermethod as following. article.adapter.ts

How do I mutate the current state in ngrx entity?

When you're using NgRx Entity, most of the reducer's cases will be as lean as in the snippet above. Within each case, you will use one of the adapter's functions to mutate the current state in a pure way. While the primary focus of NgRx Entity is to use the package in a state reducer, its generic API allows it to be used in more scenarios.

What is the difference between ngrx selectors and ngrx entity selectors?

This selectors are then ready to be used directly in a component or as a starting point for building other selectors. Though the ngrx Entity allows the writing of state, reducer and selector logic to be easier, the reducer function itself still has to be written. Using the Ngrx Entity does not avoid having to write reducer logic for each entity.

What is the entity adapter used for?

The entity adapter also provides methods for operations against an entity. These methods can change one to many records at a time. Each method returns the newly modified state if changes were made and the same state if no changes were made.


1 Answers

NgRx entity is a simple and small library to handle large arrays even thought the documentation does not explain how to use more than one entity in one state, it should be easy since the library what it does behind the scenes is just normalize the array and create a dictionary with the data.

In order to make work the state with one or more entities follow the next steps:

Start by defining a state of each entity.

interface CarState extends EntityState<Car> {
  total: number;
}

interface PlaceState extends EntityState<Place> {
  total: number;
}

Then create a state that holds the entities

export interface State {
  msg: string;
  cars: CarState;
  places: PlaceState;
}

Create the adapters for each entity state to manipulate the data and create the initial states.

const adapterCar = createEntityAdapter<Car>();
const adapterPlace = createEntityAdapter<Place>();

const carInitialState: CarState = adapterCar.getInitialState({ total: 0 });
const placeInitialState: PlaceState = adapterPlace.getInitialState({ total: 0 });

Define the initial global state

const initialState = {
  msg: 'Multiple entities in the same state',
  cars: carInitialState,
  places: placeInitialState
}

Create the reducer:

export function reducer(state: State = initialState, action: ExampleActions): State {

  switch (action.type) {

    case ExampleActionTypes.GetCarList:
      return { ...state, cars: adapterCar.addMany(action.payload, state.cars) };

    case ExampleActionTypes.GetPlaceList:
      return { ...state, places: adapterPlace.addMany(action.payload, state.places) };

    default:
      return state;
  }

}

Expose the selectors

export const selectCarState = (state: State) => state.cars;
export const selectPlaceState = (state: State) => state.places;

export const { selectAll: selectAllCars } = adapterCar.getSelectors();
export const { selectAll: selectAllPlaces } = adapterPlace.getSelectors();

That's it :)

Live example: https://stackblitz.com/edit/angular-multiple-entities-in-same-state

like image 197
marianocodes Avatar answered Sep 21 '22 15:09

marianocodes