ngrx-normalizr
Managing normalized state in ngrx applications, transparently.
This package provides a set of actions, reducers and selectors for handling normalization and denormalization of state data transparently. ngrx-normalizr uses normalizr for normalizing and denormalizing data. All normalization and denormalization is defined by the use of normalizr schemas, since that's the way normalizr works. This enables selectors to use a transparent and powerful projection of state data.
Releases will be published from the
master
branch. Go there for documentation that aligns with the npm repo version.
Installation
To install this package:
yarn add ngrx-normalizrnpm i ngrx-normalizr
Peer dependencies
ngrx-normalizr @ngrx-store as its peer dependencies, so you need to install them if not present already:
ngrx-normalizr itself does not rely on any Angular feature.
yarn add @ngrx/storenpm i @ngrx/store
Usage
Also refer to the Typedoc documentation.
To enable the normalizing reducer to store normalized data, you have to add it to your state. The best place for this might be the root state of your application, but feature states may use their own normalized state as well. Extend your state interface with the NormalizedState
interface. TheActionReducerMap
has to implement a reducer which reduces the state to a NormalizedState
.
;; const reducers: ActionReducerMap<State> = normalized /* ... other state reducers */;
If there are no other state properties, it is sufficient to add the ngrx-normalizr reducer to your state reducers or simply pass it to StoreModule.forRoot
.
const reducers: ActionReducerMap<NormalizedState> = normalized ;
Now you have a normalized
state property which will hold the normalized data. Do not worry about the weird name,
you will not have to deal with it.
Schemas
Schemas define the relations of your data. In order to normalize and denormalize data, normalizr needs to be feed with a schema. In this example, a user might have an array of pets:
; id: string; name: string; type: 'cat' | 'dog'; id: string; name: string; pets: Pet; const petSchema = 'pets';const userSchema = 'users' pets: petSchema ;
Add, set and remove data
Actions are used to set data in - and remove data from - the normalized store.
Adding data
To add data and automatically normalize it, ngrx-normalizr provides a AddData
action. This action takes an object with data
and schema
as an argument. Entities are identified by their id attribute set in the passed schema.
Existing entities will be overwritten by updated data, new entities will be added to the store. For adding related childs, an AddChildData
action is provided.
AddData
in an effect
Using @loadEffect$ = thisactions$ ;
Adding child data
Adding a related child data to a parent entity can be done with the AddChildData
action. Note that for this to work, the relation has to be defined in the schema. The action takes a couple of arguments which need to be given in an object:
data
: Array of child entities to addchildSchema
Theschema.Entity
of the child entityparentSchema
: Theschema.Entity
of the parent entityparentId
: The id of the entity to add child references to
AddChildData
in an effect
Using @addPetEffect$ = thisactions$ ;
Setting data
The SetData
action will overwrite all entities for a given schema with the normalized entities of the data
property of the action constructor argument. This action can
be used for resetting entity state data instead of adding and updating existing entities.
Removing data
To remove data, ngrx-normalizr provides a RemoveData
action.
This action takes an object with id
, schema
and an optional removeChildren
property as constructor argument. The schema entity with the given id will be removed. If removeChildren
is a map of the schema key mapped to an object property, all referenced child entities will also be removed from the store. This is handy for 1:1 relations, since only removing the parent entity may leave unused child entities in the store.
RemoveData
in an effect
Using @removeEffect$ = thisactions$ ;
Removing child data
Removing a child entity which is 1:1 related to a parent entity can be done with the RemoveChildData
action. Note that for this to work, the relation has to be defined in the schema. The action takes a couple of arguments which need to be given in an object:
id
: Id of the child entity that should be removedchildSchema
Theschema.Entity
of the child entityparentSchema
: Theschema.Entity
of the parent entityparentId
: The id of the entity to remove child references from
AddChildData
in an effect
Using @removePetEffect$ = thisactions$ ;
Action creators
For convenience, ngrx-normalizr provides an actionCreators
function which will return an object with following schema bound action creators:
setData
-(data: T[]) => SetData<T>
addData
-(data: T[]) => AddData<T>
addChildData<C>
-(data: C[], childSchema: schema.Entity, parentId: string) => AddChildData
removeData
-(id: string, removeChildren?: SchemaMap) => RemoveData
removeChildData
-(id: string, childSchema: schema.Entity, parentId: string) => RemoveChildData
Action creators could be exported along whith other feature actions:
; const creators = actionCreators<User>userSchema;const setUserData = creatorssetData;const addUserData = creatorsaddData;const removeUserData = creatorsremoveData;
Using the action creator in an Effect class:
removeUserData
action creator in an effect
Using the ; @removeEffect$ = thisactions$ ;
Query state data
ngrx-normalizr provides two simple selectors and two simple projector functions to query the state and project/denormalize the result.
Creating Schema selectors
To transparently query data from the store from a feature module, selectors are provided by the createSchemaSelectors
function.
It takes an entity schema to create schema bound selectors:
;; const schemaSelectors = createSchemaSelectors<User>userSchema;
createSchemaSelectors
will return schema bound selectors (instance of SchemaSelectors
):
getEntities
-MemoizedSelector<{}, T[]>
Returns all denormalized entities for the schemagetNormalizedEntities
-MemoizedSelector<any, EntityMap>
Returns all normalized (raw) state entities of every schema (the whole entities state)entitiesProjector
-(entities: {}, ids?: Array<string>) => T[]
Projector function for denormalizing a the set of normalized entities to an denormalized entity array. If noids
are given, all entities will be denormalized.entityProjector
-(entities: {}, id: string) => T
Projector function for denormalizing a single normalized entity with the given id
You might create several selectors with several schemas, i.e. a listView schema, which only denormalizes the data used in the list view, and a detailView schema, to completely denormalize a given entity.
Using schema selectors
Feature selectors can use the schema bound selectors and projector functions to query entity data from the store. To get all denormalized
entities, you might simply use the getEntities
selector like this:
// store.select(getUsers) will give all denormalized user entitiesconst getUsers = schemaSelectorsgetEntities;
Under the hood this does something similar to this:
// equivalent alternativeconst getUsers = ;
The entitiesProjector
simply takes an object of normalized entity data and applies the denormalization with the bound schema. Optionally an array of id strings can be passed as a second parameter to perform denormalization for the given id's only.
Composing schema selectors
To query and denormalize specific data you can use the @ngrx/store createSelectors
function and compose them with the schema bound
selectors:
; const getSelectedUserId = ; // store.select(getSelectedUser) will give the denormalized selected userconst getSelectedUser = ;
entityProjector
will simply take an object of denormalized entities and apply the denormalization with the bound schema only for the given id. Note that you might also select data from the denormalized result and providing your own selector:
const getSelectedUserWithPetsOnly = ;
Meta
Michael Krone – @DevDig – michael.krone@outlook.com and all CONTRIBUTORS
Distributed under the MIT license. See LICENSE
for more information.
https://github.com/michaelkrone/ngrx-normalizr
Contributing
- Fork it (https://github.com/michaelkrone/ngrx-normalizr)
- Create your feature branch (
git checkout -b feature/fooBar
) - Commit your changes (
git commit -am 'Add some fooBar'
) - Push to the branch (
git push origin feature/fooBar
) - Create a new Pull Request