diff --git a/.circleci/config.yml b/.circleci/config.yml index 9a1a44c7c3..c06c38e6f9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -170,7 +170,7 @@ jobs: - write_master_hash - run: name: Run Affected Builds - command: yarn nx affected --target=build --base=$(cat ~/project/master.txt) --head=$CIRCLE_SHA1 --with-deps + command: yarn nx affected --target=build --base=$(cat ~/project/master.txt) --head=$CIRCLE_SHA1 --with-deps --skip-nx-cache build-bazel: <<: *run_in_node @@ -247,7 +247,7 @@ jobs: steps: - add_ssh_keys: fingerprints: - - 'c9:c2:b4:5e:13:23:b6:6d:d8:29:3e:68:c6:40:9c:ec' + - "c9:c2:b4:5e:13:23:b6:6d:d8:29:3e:68:c6:40:9c:ec" - checkout: path: ~/docs - restore_cache: @@ -292,7 +292,7 @@ jobs: steps: - add_ssh_keys: fingerprints: - - 'c9:c2:b4:5e:13:23:b6:6d:d8:29:3e:68:c6:40:9c:ec' + - "c9:c2:b4:5e:13:23:b6:6d:d8:29:3e:68:c6:40:9c:ec" - checkout - restore_cache: keys: @@ -340,7 +340,7 @@ jobs: steps: - add_ssh_keys: fingerprints: - - 'c9:c2:b4:5e:13:23:b6:6d:d8:29:3e:68:c6:40:9c:ec' + - "c9:c2:b4:5e:13:23:b6:6d:d8:29:3e:68:c6:40:9c:ec" - checkout - restore_cache: keys: diff --git a/modules/entity/spec/sorted_state_adapter.spec.ts b/modules/entity/spec/sorted_state_adapter.spec.ts index bcb0702084..49cb868f80 100644 --- a/modules/entity/spec/sorted_state_adapter.spec.ts +++ b/modules/entity/spec/sorted_state_adapter.spec.ts @@ -361,6 +361,29 @@ describe('Sorted State Adapter', () => { }); }); + it('should let you map over one entity by id in the state', () => { + const withMany = adapter.setAll([TheGreatGatsby, AClockworkOrange], state); + + const withUpdates = adapter.mapOne( + { + id: TheGreatGatsby.id, + map: (entity) => ({ ...entity, title: 'Updated ' + entity.title }), + }, + withMany + ); + + expect(withUpdates).toEqual({ + ids: [AClockworkOrange.id, TheGreatGatsby.id], + entities: { + [TheGreatGatsby.id]: { + ...TheGreatGatsby, + title: 'Updated ' + TheGreatGatsby.title, + }, + [AClockworkOrange.id]: AClockworkOrange, + }, + }); + }); + it('should let you add one entity to the state with upsert()', () => { const withOneEntity = adapter.upsertOne(TheGreatGatsby, state); expect(withOneEntity).toEqual({ diff --git a/modules/entity/spec/unsorted_state_adapter.spec.ts b/modules/entity/spec/unsorted_state_adapter.spec.ts index e53cef695a..4be3ee20eb 100644 --- a/modules/entity/spec/unsorted_state_adapter.spec.ts +++ b/modules/entity/spec/unsorted_state_adapter.spec.ts @@ -301,6 +301,29 @@ describe('Unsorted State Adapter', () => { }); }); + it('should let you map over one entity by id in the state', () => { + const withMany = adapter.setAll([TheGreatGatsby, AClockworkOrange], state); + + const withUpdates = adapter.mapOne( + { + id: TheGreatGatsby.id, + map: (entity) => ({ ...entity, title: 'Updated ' + entity.title }), + }, + withMany + ); + + expect(withUpdates).toEqual({ + ids: [TheGreatGatsby.id, AClockworkOrange.id], + entities: { + [AClockworkOrange.id]: AClockworkOrange, + [TheGreatGatsby.id]: { + ...TheGreatGatsby, + title: 'Updated ' + TheGreatGatsby.title, + }, + }, + }); + }); + it('should let you add one entity to the state with upsert()', () => { const withOneEntity = adapter.upsertOne(TheGreatGatsby, state); expect(withOneEntity).toEqual({ diff --git a/modules/entity/src/index.ts b/modules/entity/src/index.ts index f6d374d424..3b84cf60a4 100644 --- a/modules/entity/src/index.ts +++ b/modules/entity/src/index.ts @@ -5,6 +5,7 @@ export { EntityAdapter, Update, EntityMap, + EntityMapOne, Predicate, IdSelector, Comparer, diff --git a/modules/entity/src/models.ts b/modules/entity/src/models.ts index dae9bc3f83..ed0f2fc156 100644 --- a/modules/entity/src/models.ts +++ b/modules/entity/src/models.ts @@ -29,6 +29,18 @@ export type Predicate = (entity: T) => boolean; export type EntityMap = (entity: T) => T; +export interface EntityMapOneNum { + id: number; + map: EntityMap; +} + +export interface EntityMapOneStr { + id: string; + map: EntityMap; +} + +export type EntityMapOne = EntityMapOneNum | EntityMapOneStr; + export interface EntityState { ids: string[] | number[]; entities: Dictionary; @@ -64,6 +76,7 @@ export interface EntityStateAdapter { upsertOne>(entity: T, state: S): S; upsertMany>(entities: T[], state: S): S; + mapOne>(map: EntityMapOne, state: S): S; map>(map: EntityMap, state: S): S; } diff --git a/modules/entity/src/sorted_state_adapter.ts b/modules/entity/src/sorted_state_adapter.ts index cc47587e13..311750a9d5 100644 --- a/modules/entity/src/sorted_state_adapter.ts +++ b/modules/entity/src/sorted_state_adapter.ts @@ -5,6 +5,8 @@ import { EntityStateAdapter, Update, EntityMap, + EntityMapOneNum, + EntityMapOneStr, } from './models'; import { createStateOperator, DidMutate } from './state_adapter'; import { createUnsortedStateAdapter } from './unsorted_state_adapter'; @@ -135,6 +137,24 @@ export function createSortedStateAdapter(selectId: any, sort: any): any { return updateManyMutably(updates, state); } + function mapOneMutably(map: EntityMapOneNum, state: R): DidMutate; + function mapOneMutably(map: EntityMapOneStr, state: R): DidMutate; + function mapOneMutably({ map, id }: any, state: any): DidMutate { + const entity = state.entities[id]; + if (!entity) { + return DidMutate.None; + } + + const updatedEntity = map(entity); + return updateOneMutably( + { + id: id, + changes: updatedEntity, + }, + state + ); + } + function upsertOneMutably(entity: T, state: R): DidMutate; function upsertOneMutably(entity: any, state: any): DidMutate { return upsertManyMutably([entity], state); @@ -218,5 +238,6 @@ export function createSortedStateAdapter(selectId: any, sort: any): any { updateMany: createStateOperator(updateManyMutably), upsertMany: createStateOperator(upsertManyMutably), map: createStateOperator(mapMutably), + mapOne: createStateOperator(mapOneMutably), }; } diff --git a/modules/entity/src/unsorted_state_adapter.ts b/modules/entity/src/unsorted_state_adapter.ts index fd2515e4eb..51dc91159a 100644 --- a/modules/entity/src/unsorted_state_adapter.ts +++ b/modules/entity/src/unsorted_state_adapter.ts @@ -5,6 +5,8 @@ import { Update, Predicate, EntityMap, + EntityMapOneNum, + EntityMapOneStr, } from './models'; import { createStateOperator, DidMutate } from './state_adapter'; import { selectIdValue } from './utils'; @@ -172,6 +174,24 @@ export function createUnsortedStateAdapter(selectId: IdSelector): any { return updateManyMutably(updates, state); } + function mapOneMutably(map: EntityMapOneNum, state: R): DidMutate; + function mapOneMutably(map: EntityMapOneStr, state: R): DidMutate; + function mapOneMutably({ map, id }: any, state: any): DidMutate { + const entity = state.entities[id]; + if (!entity) { + return DidMutate.None; + } + + const updatedEntity = map(entity); + return updateOneMutably( + { + id: id, + changes: updatedEntity, + }, + state + ); + } + function upsertOneMutably(entity: T, state: R): DidMutate; function upsertOneMutably(entity: any, state: any): DidMutate { return upsertManyMutably([entity], state); @@ -220,5 +240,6 @@ export function createUnsortedStateAdapter(selectId: IdSelector): any { removeOne: createStateOperator(removeOneMutably), removeMany: createStateOperator(removeManyMutably), map: createStateOperator(mapMutably), + mapOne: createStateOperator(mapOneMutably), }; } diff --git a/projects/ngrx.io/angular.json b/projects/ngrx.io/angular.json index 5415f34c73..81c802168e 100644 --- a/projects/ngrx.io/angular.json +++ b/projects/ngrx.io/angular.json @@ -1,9 +1,9 @@ { - "$schema": - "./node_modules/@angular-devkit/core/src/workspace/workspace-schema.json", + "$schema": "./node_modules/@angular-devkit/core/src/workspace/workspace-schema.json", "version": 1, "cli": { - "packageManager": "yarn" + "packageManager": "yarn", + "analytics": false }, "newProjectRoot": "content/examples", "projects": { @@ -47,7 +47,9 @@ "output": "/assets/js" } ], - "styles": ["src/styles.scss"], + "styles": [ + "src/styles.scss" + ], "scripts": [] }, "configurations": { @@ -117,7 +119,9 @@ "polyfills": "src/polyfills.ts", "tsConfig": "src/tsconfig.spec.json", "scripts": [], - "styles": ["src/styles.scss"], + "styles": [ + "src/styles.scss" + ], "assets": [ "src/assets", "src/generated", @@ -140,7 +144,10 @@ "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { - "tsConfig": ["src/tsconfig.app.json", "src/tsconfig.spec.json"], + "tsConfig": [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], "exclude": [] } } @@ -162,7 +169,9 @@ "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { - "tsConfig": ["tests/e2e/tsconfig.e2e.json"], + "tsConfig": [ + "tests/e2e/tsconfig.e2e.json" + ], "exclude": [] } } @@ -179,4 +188,4 @@ "prefix": "aio" } } -} +} \ No newline at end of file diff --git a/projects/ngrx.io/content/guide/entity/adapter.md b/projects/ngrx.io/content/guide/entity/adapter.md index 8812a6bd7e..de8296ae8c 100644 --- a/projects/ngrx.io/content/guide/entity/adapter.md +++ b/projects/ngrx.io/content/guide/entity/adapter.md @@ -83,18 +83,19 @@ The entity adapter also provides methods for operations against an entity. These 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. -- `addOne`: Add one entity to the collection -- `addMany`: Add multiple entities to the collection -- `setAll`: Replace current collection with provided collection -- `setOne`: Add or Replace one entity in the collection -- `removeOne`: Remove one entity from the collection -- `removeMany`: Remove multiple entities from the collection, by id or by predicate -- `removeAll`: Clear entity collection +- `addOne`: Add one entity to the collection. +- `addMany`: Add multiple entities to the collection. +- `setAll`: Replace current collection with provided collection. +- `setOne`: Add or Replace one entity in the collection. +- `removeOne`: Remove one entity from the collection. +- `removeMany`: Remove multiple entities from the collection, by id or by predicate. +- `removeAll`: Clear entity collection. - `updateOne`: Update one entity in the collection. Supports partial updates. - `updateMany`: Update multiple entities in the collection. Supports partial updates. - `upsertOne`: Add or Update one entity in the collection. Supports partial updates. - `upsertMany`: Add or Update multiple entities in the collection. Supports partial updates. -- `map`: Update multiple entities in the collection by defining a map function, similar to [Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) +- `mapOne`: Update one entity in the collection by defining a map function. +- `map`: Update multiple entities in the collection by defining a map function, similar to [Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). Usage: @@ -107,7 +108,7 @@ export interface User { import { createAction, props } from '@ngrx/store'; -import { Update, EntityMap, Predicate } from '@ngrx/entity'; +import { Update, EntityMap, EntityMapOne, Predicate } from '@ngrx/entity'; import { User } from '../models/user.model'; @@ -119,6 +120,7 @@ export const addUsers = createAction('[User/API] Add Users', props<{ users: User export const upsertUsers = createAction('[User/API] Upsert Users', props<{ users: User[] }>()); export const updateUser = createAction('[User/API] Update User', props<{ update: Update<User> }>()); export const updateUsers = createAction('[User/API] Update Users', props<{ updates: Update<User>[] }>()); +export const mapUser = createAction('[User/API] Map User', props<{ entityMap: EntityMapOne<User> }>()); export const mapUsers = createAction('[User/API] Map Users', props<{ entityMap: EntityMap<User> }>()); export const deleteUser = createAction('[User/API] Delete User', props<{ id: string }>()); export const deleteUsers = createAction('[User/API] Delete Users', props<{ ids: string[] }>()); @@ -168,6 +170,9 @@ const userReducer = createReducer( on(UserActions.updateUsers, (state, { updates }) => { return adapter.updateMany(updates, state); }), + on(UserActions.mapUser, (state, { entityMap }) => { + return adapter.map(entityMap, state); + }), on(UserActions.mapUsers, (state, { entityMap }) => { return adapter.map(entityMap, state); }),