/**
 * Exports reducers and selectors of the ngrx-normalizr package.
 */ import { createSelector , MemoizedSelector } from '@ngrx/store' ; import { denormalize , schema } from 'normalizr' ; import { NormalizeActionTypes } from '../actions/normalize' ; import { NormalizeChildActionPayload , NormalizeRemoveChildActionPayload } from '../index' ; /**
 * The state key under which the normalized state will be stored
 */ const STATE_KEY = 'normalized' ; /**
 * Interface describing the entities propery of a normalized state.
 * A map of schema keys wich map to a map of entity id's to entity data.
 * This corresponds to the `entities` property of a `normalizr.normalize` result.
 */ export interface EntityMap { [ key ] : { [ id ] : any ; } ; } /**
 * The state interface from which the app state should extend.
 * Holds an instance of `NormalizedEntityState` itself.
 */ export interface NormalizedState { /** The normalized state property */ normalized : NormalizedEntityState ; } /**
 * The normalized state, representing a `normalizr.normalize` result.
 * Can be selected by the provided `getNormalizedEntities` and `getResult`
 * selectors.
 */ export interface NormalizedEntityState { /**
     * The original sorting of the unnormalized data.
     * Holds all id's of the last set operation in original order.
     * Can be used to restore the original sorting of entities
     */ result : string [ ] ; /**
     * The normalized entities. Should be passed to all projector functions
     * to enable access to all entities needed.
     */ entities : EntityMap ; } /**
 * The initial state for the normalized entity state.
 */ const initialState = { result : [ ] , entities : { } } ; param state param action export function normalized ( state = initialState , action ) { switch ( action . type ) { case NormalizeActionTypes . SET_DATA : { const { result , entities } = action . payload ; return { result , entities : { ... state . entities , ... entities } } ; } case NormalizeActionTypes . ADD_DATA : { const { result , entities } = action . payload ; return { result , entities : Object . keys ( entities ) . reduce ( ( p , c ) => { p [ c ] = { ... p [ c ] , ... entities [ c ] } ; return p ; } , { ... state . entities } ) } ; } case NormalizeActionTypes . ADD_CHILD_DATA : { const { result , entities , parentSchemaKey , parentProperty , parentId } = action . payload as NormalizeChildActionPayload ; const newEntities = { ... state . entities } ; if ( getParentReferences ( newEntities , action . payload ) ) { newEntities [ parentSchemaKey ] [ parentId ] [ parentProperty ] . push ( ... result ) ; } return { result , entities : Object . keys ( entities ) . reduce ( ( p , c ) => { p [ c ] = { ... p [ c ] , ... entities [ c ] } ; return p ; } , newEntities ) } ; } case NormalizeActionTypes . UPDATE_DATA : { const { id , key , changes , result } = action . payload ; if ( ! state . entities [ key ] || ! state . entities [ key ] [ id ] ) { return state ; } const newEntities = { ... state . entities } ; Object . entries ( changes ) . forEach ( ( [ key , value ] ) => { Object . entries ( changes [ key ] ) . forEach ( ( [ id , obj ] ) => { newEntities [ key ] [ id ] = newEntities [ key ] [ id ] || { } ; Object . entries ( changes [ key ] [ id ] ) . forEach ( ( [ property , value ] ) => { if ( Array . isArray ( value ) ) { newEntities [ key ] [ id ] [ property ] . push ( ... value ) ; } else { newEntities [ key ] [ id ] [ property ] = value ; } } ) ; } ) ; } ) ; return { result , entities : newEntities } ; } case NormalizeActionTypes . REMOVE_DATA : { const { id , key , removeChildren } = action . payload ; const entities = { ... state . entities } ; const entity = entities [ key ] [ id ] ; if ( ! entity ) { return state ; } if ( removeChildren ) { Object . entries ( removeChildren ) . map ( ( [ key , entityProperty ] ) => { const child = entity [ entityProperty ] ; if ( child && entities [ key ] ) { const ids = Array . isArray ( child ) ? child : [ child ] ; ids . forEach ( ( oldId ) => delete entities [ key ] [ oldId ] ) ; } } ) ; } delete entities [ key ] [ id ] ; return { result : state . result , entities } ; } case NormalizeActionTypes . REMOVE_CHILD_DATA : { const { id , childSchemaKey , parentProperty , parentSchemaKey , parentId } = action . payload as NormalizeRemoveChildActionPayload ; const newEntities = { ... state . entities } ; const entity = newEntities [ childSchemaKey ] [ id ] ; if ( ! entity ) { return state ; } const parentRefs = getParentReferences ( newEntities , action . payload ) ; if ( parentRefs && parentRefs . indexOf ( id ) > - 1 ) { newEntities [ parentSchemaKey ] [ parentId ] [ parentProperty ] . splice ( parentRefs . indexOf ( id ) , 1 ) ; } delete newEntities [ childSchemaKey ] [ id ] ; return { ... state , entities : newEntities } ; } default : return state ; } } param state const getNormalizedState = ( state ) : NormalizedEntityState => state [ STATE_KEY ] ; /**
 * Selects all normalized entities of the state, regardless of their schema.
 * This selector should be used to enable denormalizing projector functions access
 * to all needed schema entities.
 */ export const getNormalizedEntities = createSelector ( getNormalizedState , ( state ) => state . entities ) ; /**
 * Select the result order of the last set operation.
 */ export const getResult = createSelector ( getNormalizedState , ( state ) => state . result ) ; /**
 * Generic interface for `createSchemaSelectors` return type.
 */ export interface SchemaSelectors < T > { getNormalizedEntities : MemoizedSelector < any , EntityMap > ; getEntities : MemoizedSelector < { } , T [ ] > ; entityProjector : ( entities : { } , id : string ) => T ; entitiesProjector : ( entities : { } ) => T [ ] ; } param schema export function createSchemaSelectors < T > ( schema ) { return { /**
         * Select all entities, regardless of their schema, exported for convenience.
         */ getNormalizedEntities , /**
         * Select all entities and perform a denormalization based on the given schema.
         */ getEntities : createEntitiesSelector < T > ( schema ) , /**
         * Uses the given schema to denormalize an entity by the given id
         */ entityProjector : createEntityProjector < T > ( schema ) , /**
         * Uses the given schema to denormalize all given entities
         */ entitiesProjector : createEntitiesProjector < T > ( schema ) } ; } param schema function createEntitiesSelector < T > ( schema ) { return createSelector ( getNormalizedEntities , createEntitiesProjector < T > ( schema ) ) ; } param schema function createEntityProjector < T > ( schema ) { return ( entities , id ) => createSingleDenormalizer ( schema ) ( entities , id ) as T ; } param schema function createEntitiesProjector < T > ( schema ) { return ( entities , ids ? ) => createMultipleDenormalizer ( schema ) ( entities , ids ) as T [ ] ; } param schema function createSingleDenormalizer ( schema ) { const key = schema . key ; return ( entities , id ) => { if ( ! entities || ! entities [ key ] ) { return ; } const denormalized = denormalize ( { [ key ] : [ id ] } , { [ key ] : [ schema ] } , entities ) ; return denormalized [ key ] [ 0 ] ; } ; } param schema function createMultipleDenormalizer ( schema ) { const key = schema . key ; return ( entities , ids ? ) => { if ( ! entities || ! entities [ key ] ) { return ; } const data = ids ? { [ key ] : ids } : { [ key ] : Object . keys ( entities [ key ] ) } ; const denormalized = denormalize ( data , { [ key ] : [ schema ] } , entities ) ; return denormalized [ key ] ; } ; } private param entities param payload function getParentReferences ( entities , payload ) { const { parentSchemaKey , parentProperty , parentId } = payload ; if ( entities [ parentSchemaKey ] && entities [ parentSchemaKey ] [ parentId ] && entities [ parentSchemaKey ] [ parentId ] [ parentProperty ] && Array . isArray ( entities [ parentSchemaKey ] [ parentId ] [ parentProperty ] ) ) { return entities [ parentSchemaKey ] [ parentId ] [ parentProperty ] ; } }