Skip to content

Commit d1891ad

Browse files
feat(entity): add mapOne adapter method (#2628)
Closes #2538
1 parent bb309e0 commit d1891ad

File tree

9 files changed

+137
-21
lines changed

9 files changed

+137
-21
lines changed

.circleci/config.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ jobs:
170170
- write_master_hash
171171
- run:
172172
name: Run Affected Builds
173-
command: yarn nx affected --target=build --base=$(cat ~/project/master.txt) --head=$CIRCLE_SHA1 --with-deps
173+
command: yarn nx affected --target=build --base=$(cat ~/project/master.txt) --head=$CIRCLE_SHA1 --with-deps --skip-nx-cache
174174

175175
build-bazel:
176176
<<: *run_in_node
@@ -247,7 +247,7 @@ jobs:
247247
steps:
248248
- add_ssh_keys:
249249
fingerprints:
250-
- 'c9:c2:b4:5e:13:23:b6:6d:d8:29:3e:68:c6:40:9c:ec'
250+
- "c9:c2:b4:5e:13:23:b6:6d:d8:29:3e:68:c6:40:9c:ec"
251251
- checkout:
252252
path: ~/docs
253253
- restore_cache:
@@ -292,7 +292,7 @@ jobs:
292292
steps:
293293
- add_ssh_keys:
294294
fingerprints:
295-
- 'c9:c2:b4:5e:13:23:b6:6d:d8:29:3e:68:c6:40:9c:ec'
295+
- "c9:c2:b4:5e:13:23:b6:6d:d8:29:3e:68:c6:40:9c:ec"
296296
- checkout
297297
- restore_cache:
298298
keys:
@@ -340,7 +340,7 @@ jobs:
340340
steps:
341341
- add_ssh_keys:
342342
fingerprints:
343-
- 'c9:c2:b4:5e:13:23:b6:6d:d8:29:3e:68:c6:40:9c:ec'
343+
- "c9:c2:b4:5e:13:23:b6:6d:d8:29:3e:68:c6:40:9c:ec"
344344
- checkout
345345
- restore_cache:
346346
keys:

modules/entity/spec/sorted_state_adapter.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,29 @@ describe('Sorted State Adapter', () => {
361361
});
362362
});
363363

364+
it('should let you map over one entity by id in the state', () => {
365+
const withMany = adapter.setAll([TheGreatGatsby, AClockworkOrange], state);
366+
367+
const withUpdates = adapter.mapOne(
368+
{
369+
id: TheGreatGatsby.id,
370+
map: (entity) => ({ ...entity, title: 'Updated ' + entity.title }),
371+
},
372+
withMany
373+
);
374+
375+
expect(withUpdates).toEqual({
376+
ids: [AClockworkOrange.id, TheGreatGatsby.id],
377+
entities: {
378+
[TheGreatGatsby.id]: {
379+
...TheGreatGatsby,
380+
title: 'Updated ' + TheGreatGatsby.title,
381+
},
382+
[AClockworkOrange.id]: AClockworkOrange,
383+
},
384+
});
385+
});
386+
364387
it('should let you add one entity to the state with upsert()', () => {
365388
const withOneEntity = adapter.upsertOne(TheGreatGatsby, state);
366389
expect(withOneEntity).toEqual({

modules/entity/spec/unsorted_state_adapter.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,29 @@ describe('Unsorted State Adapter', () => {
301301
});
302302
});
303303

304+
it('should let you map over one entity by id in the state', () => {
305+
const withMany = adapter.setAll([TheGreatGatsby, AClockworkOrange], state);
306+
307+
const withUpdates = adapter.mapOne(
308+
{
309+
id: TheGreatGatsby.id,
310+
map: (entity) => ({ ...entity, title: 'Updated ' + entity.title }),
311+
},
312+
withMany
313+
);
314+
315+
expect(withUpdates).toEqual({
316+
ids: [TheGreatGatsby.id, AClockworkOrange.id],
317+
entities: {
318+
[AClockworkOrange.id]: AClockworkOrange,
319+
[TheGreatGatsby.id]: {
320+
...TheGreatGatsby,
321+
title: 'Updated ' + TheGreatGatsby.title,
322+
},
323+
},
324+
});
325+
});
326+
304327
it('should let you add one entity to the state with upsert()', () => {
305328
const withOneEntity = adapter.upsertOne(TheGreatGatsby, state);
306329
expect(withOneEntity).toEqual({

modules/entity/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export {
55
EntityAdapter,
66
Update,
77
EntityMap,
8+
EntityMapOne,
89
Predicate,
910
IdSelector,
1011
Comparer,

modules/entity/src/models.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ export type Predicate<T> = (entity: T) => boolean;
2929

3030
export type EntityMap<T> = (entity: T) => T;
3131

32+
export interface EntityMapOneNum<T> {
33+
id: number;
34+
map: EntityMap<T>;
35+
}
36+
37+
export interface EntityMapOneStr<T> {
38+
id: string;
39+
map: EntityMap<T>;
40+
}
41+
42+
export type EntityMapOne<T> = EntityMapOneNum<T> | EntityMapOneStr<T>;
43+
3244
export interface EntityState<T> {
3345
ids: string[] | number[];
3446
entities: Dictionary<T>;
@@ -64,6 +76,7 @@ export interface EntityStateAdapter<T> {
6476
upsertOne<S extends EntityState<T>>(entity: T, state: S): S;
6577
upsertMany<S extends EntityState<T>>(entities: T[], state: S): S;
6678

79+
mapOne<S extends EntityState<T>>(map: EntityMapOne<T>, state: S): S;
6780
map<S extends EntityState<T>>(map: EntityMap<T>, state: S): S;
6881
}
6982

modules/entity/src/sorted_state_adapter.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
EntityStateAdapter,
66
Update,
77
EntityMap,
8+
EntityMapOneNum,
9+
EntityMapOneStr,
810
} from './models';
911
import { createStateOperator, DidMutate } from './state_adapter';
1012
import { createUnsortedStateAdapter } from './unsorted_state_adapter';
@@ -135,6 +137,24 @@ export function createSortedStateAdapter<T>(selectId: any, sort: any): any {
135137
return updateManyMutably(updates, state);
136138
}
137139

140+
function mapOneMutably(map: EntityMapOneNum<T>, state: R): DidMutate;
141+
function mapOneMutably(map: EntityMapOneStr<T>, state: R): DidMutate;
142+
function mapOneMutably({ map, id }: any, state: any): DidMutate {
143+
const entity = state.entities[id];
144+
if (!entity) {
145+
return DidMutate.None;
146+
}
147+
148+
const updatedEntity = map(entity);
149+
return updateOneMutably(
150+
{
151+
id: id,
152+
changes: updatedEntity,
153+
},
154+
state
155+
);
156+
}
157+
138158
function upsertOneMutably(entity: T, state: R): DidMutate;
139159
function upsertOneMutably(entity: any, state: any): DidMutate {
140160
return upsertManyMutably([entity], state);
@@ -218,5 +238,6 @@ export function createSortedStateAdapter<T>(selectId: any, sort: any): any {
218238
updateMany: createStateOperator(updateManyMutably),
219239
upsertMany: createStateOperator(upsertManyMutably),
220240
map: createStateOperator(mapMutably),
241+
mapOne: createStateOperator(mapOneMutably),
221242
};
222243
}

modules/entity/src/unsorted_state_adapter.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
Update,
66
Predicate,
77
EntityMap,
8+
EntityMapOneNum,
9+
EntityMapOneStr,
810
} from './models';
911
import { createStateOperator, DidMutate } from './state_adapter';
1012
import { selectIdValue } from './utils';
@@ -172,6 +174,24 @@ export function createUnsortedStateAdapter<T>(selectId: IdSelector<T>): any {
172174
return updateManyMutably(updates, state);
173175
}
174176

177+
function mapOneMutably(map: EntityMapOneNum<T>, state: R): DidMutate;
178+
function mapOneMutably(map: EntityMapOneStr<T>, state: R): DidMutate;
179+
function mapOneMutably({ map, id }: any, state: any): DidMutate {
180+
const entity = state.entities[id];
181+
if (!entity) {
182+
return DidMutate.None;
183+
}
184+
185+
const updatedEntity = map(entity);
186+
return updateOneMutably(
187+
{
188+
id: id,
189+
changes: updatedEntity,
190+
},
191+
state
192+
);
193+
}
194+
175195
function upsertOneMutably(entity: T, state: R): DidMutate;
176196
function upsertOneMutably(entity: any, state: any): DidMutate {
177197
return upsertManyMutably([entity], state);
@@ -220,5 +240,6 @@ export function createUnsortedStateAdapter<T>(selectId: IdSelector<T>): any {
220240
removeOne: createStateOperator(removeOneMutably),
221241
removeMany: createStateOperator(removeManyMutably),
222242
map: createStateOperator(mapMutably),
243+
mapOne: createStateOperator(mapOneMutably),
223244
};
224245
}

projects/ngrx.io/angular.json

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
2-
"$schema":
3-
"./node_modules/@angular-devkit/core/src/workspace/workspace-schema.json",
2+
"$schema": "./node_modules/@angular-devkit/core/src/workspace/workspace-schema.json",
43
"version": 1,
54
"cli": {
6-
"packageManager": "yarn"
5+
"packageManager": "yarn",
6+
"analytics": false
77
},
88
"newProjectRoot": "content/examples",
99
"projects": {
@@ -47,7 +47,9 @@
4747
"output": "/assets/js"
4848
}
4949
],
50-
"styles": ["src/styles.scss"],
50+
"styles": [
51+
"src/styles.scss"
52+
],
5153
"scripts": []
5254
},
5355
"configurations": {
@@ -117,7 +119,9 @@
117119
"polyfills": "src/polyfills.ts",
118120
"tsConfig": "src/tsconfig.spec.json",
119121
"scripts": [],
120-
"styles": ["src/styles.scss"],
122+
"styles": [
123+
"src/styles.scss"
124+
],
121125
"assets": [
122126
"src/assets",
123127
"src/generated",
@@ -140,7 +144,10 @@
140144
"lint": {
141145
"builder": "@angular-devkit/build-angular:tslint",
142146
"options": {
143-
"tsConfig": ["src/tsconfig.app.json", "src/tsconfig.spec.json"],
147+
"tsConfig": [
148+
"src/tsconfig.app.json",
149+
"src/tsconfig.spec.json"
150+
],
144151
"exclude": []
145152
}
146153
}
@@ -162,7 +169,9 @@
162169
"lint": {
163170
"builder": "@angular-devkit/build-angular:tslint",
164171
"options": {
165-
"tsConfig": ["tests/e2e/tsconfig.e2e.json"],
172+
"tsConfig": [
173+
"tests/e2e/tsconfig.e2e.json"
174+
],
166175
"exclude": []
167176
}
168177
}
@@ -179,4 +188,4 @@
179188
"prefix": "aio"
180189
}
181190
}
182-
}
191+
}

projects/ngrx.io/content/guide/entity/adapter.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,18 +83,19 @@ The entity adapter also provides methods for operations against an entity. These
8383
one to many records at a time. Each method returns the newly modified state if changes were made and the same
8484
state if no changes were made.
8585

86-
- `addOne`: Add one entity to the collection
87-
- `addMany`: Add multiple entities to the collection
88-
- `setAll`: Replace current collection with provided collection
89-
- `setOne`: Add or Replace one entity in the collection
90-
- `removeOne`: Remove one entity from the collection
91-
- `removeMany`: Remove multiple entities from the collection, by id or by predicate
92-
- `removeAll`: Clear entity collection
86+
- `addOne`: Add one entity to the collection.
87+
- `addMany`: Add multiple entities to the collection.
88+
- `setAll`: Replace current collection with provided collection.
89+
- `setOne`: Add or Replace one entity in the collection.
90+
- `removeOne`: Remove one entity from the collection.
91+
- `removeMany`: Remove multiple entities from the collection, by id or by predicate.
92+
- `removeAll`: Clear entity collection.
9393
- `updateOne`: Update one entity in the collection. Supports partial updates.
9494
- `updateMany`: Update multiple entities in the collection. Supports partial updates.
9595
- `upsertOne`: Add or Update one entity in the collection. Supports partial updates.
9696
- `upsertMany`: Add or Update multiple entities in the collection. Supports partial updates.
97-
- `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)
97+
- `mapOne`: Update one entity in the collection by defining a map function.
98+
- `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).
9899

99100
Usage:
100101

@@ -107,7 +108,7 @@ export interface User {
107108

108109
<code-example header="user.actions.ts">
109110
import { createAction, props } from '@ngrx/store';
110-
import { Update, EntityMap, Predicate } from '@ngrx/entity';
111+
import { Update, EntityMap, EntityMapOne, Predicate } from '@ngrx/entity';
111112

112113
import { User } from '../models/user.model';
113114

@@ -119,6 +120,7 @@ export const addUsers = createAction('[User/API] Add Users', props<{ users: User
119120
export const upsertUsers = createAction('[User/API] Upsert Users', props<{ users: User[] }>());
120121
export const updateUser = createAction('[User/API] Update User', props<{ update: Update&lt;User&gt; }>());
121122
export const updateUsers = createAction('[User/API] Update Users', props<{ updates: Update&lt;User&gt;[] }>());
123+
export const mapUser = createAction('[User/API] Map User', props<{ entityMap: EntityMapOne&lt;User&gt; }>());
122124
export const mapUsers = createAction('[User/API] Map Users', props<{ entityMap: EntityMap&lt;User&gt; }>());
123125
export const deleteUser = createAction('[User/API] Delete User', props<{ id: string }>());
124126
export const deleteUsers = createAction('[User/API] Delete Users', props<{ ids: string[] }>());
@@ -168,6 +170,9 @@ const userReducer = createReducer(
168170
on(UserActions.updateUsers, (state, { updates }) => {
169171
return adapter.updateMany(updates, state);
170172
}),
173+
on(UserActions.mapUser, (state, { entityMap }) => {
174+
return adapter.map(entityMap, state);
175+
}),
171176
on(UserActions.mapUsers, (state, { entityMap }) => {
172177
return adapter.map(entityMap, state);
173178
}),

0 commit comments

Comments
 (0)