diff --git a/README.md b/README.md index f0e4495f7..e986f0223 100644 --- a/README.md +++ b/README.md @@ -473,9 +473,22 @@ store.select(selectUser, 1).subsribe(user => { }); ``` +### Gathering information of a selector + +Besides the `release` function every selector provides information about itself. + +* `ngrxEntityRelationship` - name of its function: `rootEntity`, `rootEntities`, `relatedEntity`, `childEntity` and `childrenEntities`. +* `collectionSelector` - a function that returns a collection of its entity. +* `idSelector` - a function that returns the id of the related entity. +* `relationships` - an array of passed relationships. + +In case of relationship functions there are two more keys +* `keyId` - a name of the keyId field. +* `keyValue` - a name of the keyValue field. + ## Additional examples -Of course we can select as many relationships as we want until we have a field with a related id. +Of course, we can select as many relationships as we want until we have a field with a related id. Check how `childrenEntities` works. It gathers entities based on a parent field. ```typescript diff --git a/src/childEntity.ts b/src/childEntity.ts index ddce05b5c..40fb8ec81 100644 --- a/src/childEntity.ts +++ b/src/childEntity.ts @@ -38,7 +38,7 @@ export function childEntity< let relationships: Array> = [...arguments]; relationships = relationships.slice(3); - const {collection: funcSelector, id: idSelector} = normalizeSelector(featureSelector); + const {collection: collectionSelector, id: idSelector} = normalizeSelector(featureSelector); const callback = ( cacheLevel: string, @@ -47,7 +47,7 @@ export function childEntity< source: PARENT_ENTITY, idParentSelector: ID_SELECTOR, ) => { - const featureState = funcSelector(state); + const featureState = collectionSelector(state); const parentId = idParentSelector(source); let cacheDataLevel = cache.get(cacheLevel); @@ -75,7 +75,7 @@ export function childEntity< } idChecks = new Map(); const idChecksEntities = new Map(); - idChecks.set(funcSelector, idChecksEntities); + idChecks.set(collectionSelector, idChecksEntities); idChecksEntities.set(null, featureState.entities); cacheDataLevel.set(`!${parentId}`, [idChecks, id]); } @@ -97,7 +97,7 @@ export function childEntity< value = undefined; checks = new Map(); const checksEntities = new Map(); - checks.set(funcSelector, checksEntities); + checks.set(collectionSelector, checksEntities); checksEntities.set(null, featureState.entities); checksEntities.set(id, featureState.entities[id]); @@ -118,7 +118,11 @@ export function childEntity< return cacheHash; }; callback.ngrxEntityRelationship = 'childEntity'; + callback.collectionSelector = collectionSelector; callback.idSelector = idSelector; + callback.relationships = relationships; + callback.keyId = keyId; + callback.keyValue = keyValue; callback.release = () => { for (const relationship of relationships) { relationship.release(); diff --git a/src/childrenEntities.ts b/src/childrenEntities.ts index e42bc4074..f48bfc385 100644 --- a/src/childrenEntities.ts +++ b/src/childrenEntities.ts @@ -38,7 +38,7 @@ export function childrenEntities< let relationships: Array> = [...arguments]; relationships = relationships.slice(3); - const {collection: funcSelector, id: idSelector} = normalizeSelector(featureSelector); + const {collection: collectionSelector, id: idSelector} = normalizeSelector(featureSelector); const emptyResult: Map = new Map(); const callback = ( @@ -48,7 +48,7 @@ export function childrenEntities< source: PARENT_ENTITY, idParentSelector: ID_SELECTOR, ) => { - const featureState = funcSelector(state); + const featureState = collectionSelector(state); const parentId = idParentSelector(source); let cacheDataLevel = cache.get(cacheLevel); @@ -78,7 +78,7 @@ export function childrenEntities< } idsChecks = new Map(); const idsChecksEntities = new Map(); - idsChecks.set(funcSelector, idsChecksEntities); + idsChecks.set(collectionSelector, idsChecksEntities); idsChecksEntities.set(null, featureState.entities); cacheDataLevel.set(`!${parentId}`, [idsChecks, ids]); } @@ -105,7 +105,7 @@ export function childrenEntities< value = []; checks = new Map(); const checksEntities = new Map(); - checks.set(funcSelector, checksEntities); + checks.set(collectionSelector, checksEntities); checksEntities.set(null, featureState.entities); for (const id of ids) { checksEntities.set(id, featureState.entities[id]); @@ -125,7 +125,7 @@ export function childrenEntities< entityValue = {...featureState.entities[id]} as RELATED_ENTITY; entityChecks = new Map(); const entityChecksEntities = new Map(); - entityChecks.set(funcSelector, entityChecksEntities); + entityChecks.set(collectionSelector, entityChecksEntities); entityChecksEntities.set(null, featureState.entities); entityChecksEntities.set(id, featureState.entities[id]); @@ -147,7 +147,11 @@ export function childrenEntities< return cacheHash; }; callback.ngrxEntityRelationship = 'childrenEntities'; + callback.collectionSelector = collectionSelector; callback.idSelector = idSelector; + callback.relationships = relationships; + callback.keyId = keyId; + callback.keyValue = keyValue; callback.release = () => { emptyResult.clear(); for (const relationship of relationships) { diff --git a/src/relatedEntity.ts b/src/relatedEntity.ts index 54f46b406..9272623dd 100644 --- a/src/relatedEntity.ts +++ b/src/relatedEntity.ts @@ -41,7 +41,7 @@ export function relatedEntity< let relationships: Array> = [...arguments]; relationships = relationships.slice(3); - const {collection: funcSelector, id: idSelector} = normalizeSelector(featureSelector); + const {collection: collectionSelector, id: idSelector} = normalizeSelector(featureSelector); const emptyResult: Map = new Map(); const callback = (cacheLevel: string, state: STORE, cache: CACHE, source: PARENT_ENTITY) => { @@ -51,7 +51,7 @@ export function relatedEntity< return; } - const featureState = funcSelector(state); + const featureState = collectionSelector(state); let cacheDataLevel = cache.get(cacheLevel); if (!cacheDataLevel) { @@ -95,7 +95,7 @@ export function relatedEntity< value = undefined; checks = new Map(); const checkIds = new Map(); - checks.set(funcSelector, checkIds); + checks.set(collectionSelector, checkIds); checkIds.set(null, featureState.entities); for (const id of ids) { checkIds.set(id, featureState.entities[id]); @@ -119,7 +119,7 @@ export function relatedEntity< entityValue = {...featureState.entities[id]} as RELATED_ENTITY; entityChecks = new Map(); const entityCheckIds = new Map(); - entityChecks.set(funcSelector, entityCheckIds); + entityChecks.set(collectionSelector, entityCheckIds); entityCheckIds.set(null, featureState.entities); entityCheckIds.set(id, featureState.entities[id]); @@ -142,7 +142,11 @@ export function relatedEntity< return cacheHash; }; callback.ngrxEntityRelationship = 'relatedEntity'; + callback.collectionSelector = collectionSelector; callback.idSelector = idSelector; + callback.relationships = relationships; + callback.keyId = keyId; + callback.keyValue = keyValue; callback.release = () => { emptyResult.clear(); for (const relationship of relationships) { diff --git a/src/rootEntities.ts b/src/rootEntities.ts index 235d7d568..f2746cd3e 100644 --- a/src/rootEntities.ts +++ b/src/rootEntities.ts @@ -53,7 +53,9 @@ export function rootEntities( return value; }; callback.ngrxEntityRelationship = 'rootEntities'; + callback.collectionSelector = rootSelector.collectionSelector; callback.idSelector = rootSelector.idSelector; + callback.relationships = rootSelector.relationships; callback.release = () => { cacheMap.clear(); rootSelector.release(); diff --git a/src/rootEntity.ts b/src/rootEntity.ts index dd24a9b8f..31b6000ea 100644 --- a/src/rootEntity.ts +++ b/src/rootEntity.ts @@ -33,7 +33,7 @@ export function rootEntity( transformer = deside; relationships = relationships.slice(1); } - const {collection: funcSelector, id: idSelector} = normalizeSelector(featureSelector); + const {collection: collectionSelector, id: idSelector} = normalizeSelector(featureSelector); const cacheLevel = '0'; const cache: CACHE = new Map(); @@ -43,7 +43,7 @@ export function rootEntity( return; } - const featureState = funcSelector(state); + const featureState = collectionSelector(state); let cacheDataLevel = cache.get(cacheLevel); if (!cacheDataLevel) { cacheDataLevel = new Map(); @@ -66,7 +66,7 @@ export function rootEntity( value = undefined; checks = new Map(); const checkIds = new Map(); - checks.set(funcSelector, checkIds); + checks.set(collectionSelector, checkIds); checkIds.set(null, featureState.entities); checkIds.set(id, featureState.entities[id]); @@ -94,7 +94,9 @@ export function rootEntity( return value; }; callback.ngrxEntityRelationship = 'rootEntity'; + callback.collectionSelector = collectionSelector; callback.idSelector = idSelector; + callback.relationships = relationships; callback.release = () => { cache.clear(); for (const relationship of relationships) { diff --git a/src/types.ts b/src/types.ts index d0ce9c1fa..0a3953537 100644 --- a/src/types.ts +++ b/src/types.ts @@ -41,17 +41,21 @@ export type CACHE_CHECKS = Map; export type CACHE_CHECKS_SET = Map>, CACHE_CHECKS>; export type CACHE = Map, UNKNOWN]>>; -export type HANDLER_ROOT_ENTITY = { +export type HANDLER_ROOT_ENTITY = { (state: S, id: I): undefined | T; ngrxEntityRelationship: string; - idSelector: ID_SELECTOR; + collectionSelector: STORE_SELECTOR>; + idSelector: ID_SELECTOR; + relationships: Array>; release(): void; }; -export type HANDLER_ROOT_ENTITIES = { +export type HANDLER_ROOT_ENTITIES = { (state: S, id: Array): Array; ngrxEntityRelationship: string; - idSelector: ID_SELECTOR; + collectionSelector: STORE_SELECTOR>; + idSelector: ID_SELECTOR; + relationships: Array>; release(): void; }; @@ -64,7 +68,11 @@ export type HANDLER_RELATED_ENTITY = { sourceIdSelector: ID_SELECTOR, ): string | undefined; ngrxEntityRelationship: string; + collectionSelector: STORE_SELECTOR>; idSelector: ID_SELECTOR; + relationships: Array>; + keyId: keyof any; + keyValue: keyof any; release(): void; }; diff --git a/test/childEntity.spec.ts b/test/childEntity.spec.ts index 47110aa87..a1723b956 100644 --- a/test/childEntity.spec.ts +++ b/test/childEntity.spec.ts @@ -9,14 +9,17 @@ describe('childEntity', () => { child?: Entity; }; - it('marks callback with ngrxEntityRelationship key', () => { - const actual = childEntity(jasmine.createSpy(), '', ''); + it('marks callback with ngrxEntityRelationship key and passed args', () => { + const rel1: any = Symbol(); + const rel2: any = Symbol(); + const featureSelector = jasmine.createSpy(); + const actual = childEntity(featureSelector, 'myKeyId', 'myKeyValue', rel1, rel2); expect(actual).toEqual(jasmine.any(Function)); - expect(actual).toEqual( - jasmine.objectContaining({ - ngrxEntityRelationship: 'childEntity', - }), - ); + expect(actual.ngrxEntityRelationship).toEqual('childEntity'); + expect(actual.collectionSelector).toBe(featureSelector); + expect(actual.keyId).toEqual('myKeyId'); + expect(actual.keyValue).toEqual('myKeyValue'); + expect(actual.relationships).toEqual([rel1, rel2]); }); it('does not set anything if there is no child entity', () => { diff --git a/test/childrenEntities.spec.ts b/test/childrenEntities.spec.ts index b2eb21306..6c901c12a 100644 --- a/test/childrenEntities.spec.ts +++ b/test/childrenEntities.spec.ts @@ -9,14 +9,17 @@ describe('childrenEntities', () => { child?: Array; }; - it('marks callback with ngrxEntityRelationship key', () => { - const actual = childrenEntities(jasmine.createSpy(), '', ''); + it('marks callback with ngrxEntityRelationship key and passed args', () => { + const rel1: any = Symbol(); + const rel2: any = Symbol(); + const featureSelector = jasmine.createSpy(); + const actual = childrenEntities(featureSelector, 'myKeyId', 'myKeyValue', rel1, rel2); expect(actual).toEqual(jasmine.any(Function)); - expect(actual).toEqual( - jasmine.objectContaining({ - ngrxEntityRelationship: 'childrenEntities', - }), - ); + expect(actual.ngrxEntityRelationship).toEqual('childrenEntities'); + expect(actual.collectionSelector).toBe(featureSelector); + expect(actual.keyId).toEqual('myKeyId'); + expect(actual.keyValue).toEqual('myKeyValue'); + expect(actual.relationships).toEqual([rel1, rel2]); }); it('set an empty array if there are no child entities', () => { diff --git a/test/relatedEntity.spec.ts b/test/relatedEntity.spec.ts index c7c3ddb8e..36f2f710a 100644 --- a/test/relatedEntity.spec.ts +++ b/test/relatedEntity.spec.ts @@ -11,14 +11,23 @@ describe('relatedEntity', () => { parentsId?: Array; }; - it('marks callback with ngrxEntityRelationship key', () => { - const actual = relatedEntity(jasmine.createSpy(), '', ''); - expect(actual).toEqual(jasmine.any(Function)); - expect(actual).toEqual( - jasmine.objectContaining({ - ngrxEntityRelationship: 'relatedEntity', - }), + it('marks callback with ngrxEntityRelationship key and passed args', () => { + const rel1: any = Symbol(); + const rel2: any = Symbol(); + const featureSelector = jasmine.createSpy(); + const actual = relatedEntity( + featureSelector, + 'myKeyId', + 'myKeyValue', + rel1, + rel2, ); + expect(actual).toEqual(jasmine.any(Function)); + expect(actual.ngrxEntityRelationship).toEqual('relatedEntity'); + expect(actual.collectionSelector).toBe(featureSelector); + expect(actual.keyId).toEqual('myKeyId'); + expect(actual.keyValue).toEqual('myKeyValue'); + expect(actual.relationships).toEqual([rel1, rel2]); }); it('does not set anything if the related id is falsy', () => { diff --git a/test/rootEntities.spec.ts b/test/rootEntities.spec.ts index f69603584..8ba115d41 100644 --- a/test/rootEntities.spec.ts +++ b/test/rootEntities.spec.ts @@ -15,13 +15,16 @@ describe('rootEntities', () => { }); it('marks callback with ngrxEntityRelationship key', () => { - const actual = rootEntities(jasmine.createSpy() as any); + const rootSelector: any = jasmine.createSpy(); + rootSelector.relationships = Symbol(); + rootSelector.collectionSelector = Symbol(); + rootSelector.idSelector = Symbol(); + const actual = rootEntities(rootSelector); expect(actual).toEqual(jasmine.any(Function)); - expect(actual).toEqual( - jasmine.objectContaining({ - ngrxEntityRelationship: 'rootEntities', - }), - ); + expect(actual.ngrxEntityRelationship).toEqual('rootEntities'); + expect(actual.collectionSelector).toBe(rootSelector.collectionSelector); + expect(actual.idSelector).toBe(rootSelector.idSelector); + expect(actual.relationships).toBe(rootSelector.relationships); }); it('returns the same empty array on no ids', () => { diff --git a/test/rootEntity.spec.ts b/test/rootEntity.spec.ts index 4a856f494..b5c2a0f3d 100644 --- a/test/rootEntity.spec.ts +++ b/test/rootEntity.spec.ts @@ -15,13 +15,15 @@ describe('rootEntity', () => { }); it('marks callback with ngrxEntityRelationship key', () => { - const actual = rootEntity(jasmine.createSpy()); + const rel1: any = Symbol(); + const rel2: any = Symbol(); + const featureSelector = jasmine.createSpy(); + // because rels aren't real rels we need to pass a transformer. + const actual = rootEntity(featureSelector, () => null, rel1, rel2); expect(actual).toEqual(jasmine.any(Function)); - expect(actual).toEqual( - jasmine.objectContaining({ - ngrxEntityRelationship: 'rootEntity', - }), - ); + expect(actual.collectionSelector).toBe(featureSelector); + expect(actual.ngrxEntityRelationship).toEqual('rootEntity'); + expect(actual.relationships).toEqual([rel1, rel2]); }); it('returns undefined when the id is falsy', () => {