Skip to content

Commit 4b4bb85

Browse files
authored
feat(entity): add 'setOne' method to entity adapter (#2410)
Closes #2369
1 parent 4dcba7d commit 4b4bb85

File tree

7 files changed

+96
-0
lines changed

7 files changed

+96
-0
lines changed

modules/entity/spec/fixtures/book.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const deepFreeze = require('deep-freeze');
33
export interface BookModel {
44
id: string;
55
title: string;
6+
description?: string;
67
}
78

89
export const AClockworkOrange: BookModel = deepFreeze({
@@ -18,4 +19,5 @@ export const AnimalFarm: BookModel = deepFreeze({
1819
export const TheGreatGatsby: BookModel = deepFreeze({
1920
id: 'tgg',
2021
title: 'The Great Gatsby',
22+
description: 'A 1925 novel written by American author F. Scott Fitzgerald',
2123
});

modules/entity/spec/sorted_state_adapter.spec.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,4 +407,36 @@ describe('Sorted State Adapter', () => {
407407
},
408408
});
409409
});
410+
411+
it('should let you add one entity to the state with setOne()', () => {
412+
const withOneEntity = adapter.setOne(TheGreatGatsby, state);
413+
expect(withOneEntity).toEqual({
414+
ids: [TheGreatGatsby.id],
415+
entities: {
416+
[TheGreatGatsby.id]: TheGreatGatsby,
417+
},
418+
});
419+
});
420+
421+
it('should let you replace an entity in the state with setOne()', () => {
422+
const withMany = adapter.addOne(
423+
TheGreatGatsby,
424+
adapter.addOne(AnimalFarm, adapter.addOne(AClockworkOrange, state))
425+
);
426+
const updatedBook = {
427+
id: TheGreatGatsby.id,
428+
title: 'A New Hope',
429+
description: undefined,
430+
};
431+
432+
const withUpdates = adapter.setOne(updatedBook, withMany);
433+
expect(withUpdates).toEqual({
434+
ids: [AClockworkOrange.id, updatedBook.id, AnimalFarm.id],
435+
entities: {
436+
[AClockworkOrange.id]: AClockworkOrange,
437+
[updatedBook.id]: updatedBook,
438+
[AnimalFarm.id]: AnimalFarm,
439+
},
440+
});
441+
});
410442
});

modules/entity/spec/unsorted_state_adapter.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,4 +347,31 @@ describe('Unsorted State Adapter', () => {
347347
},
348348
});
349349
});
350+
351+
it('should let you add one entity to the state with setOne()', () => {
352+
const withOneEntity = adapter.setOne(TheGreatGatsby, state);
353+
expect(withOneEntity).toEqual({
354+
ids: [TheGreatGatsby.id],
355+
entities: {
356+
[TheGreatGatsby.id]: TheGreatGatsby,
357+
},
358+
});
359+
});
360+
361+
it('should let you replace an entity in the state with setOne()', () => {
362+
const withOne = adapter.addOne(TheGreatGatsby, state);
363+
const updatedBook = {
364+
id: TheGreatGatsby.id,
365+
title: 'A New Hope',
366+
description: undefined,
367+
};
368+
369+
const withUpdates = adapter.setOne(updatedBook, withOne);
370+
expect(withUpdates).toEqual({
371+
ids: [TheGreatGatsby.id],
372+
entities: {
373+
[TheGreatGatsby.id]: updatedBook,
374+
},
375+
});
376+
});
350377
});

modules/entity/src/models.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export interface EntityStateAdapter<T> {
5050
addAll<S extends EntityState<T>>(entities: T[], state: S): S;
5151

5252
setAll<S extends EntityState<T>>(entities: T[], state: S): S;
53+
setOne<S extends EntityState<T>>(entity: T, state: S): S;
5354

5455
removeOne<S extends EntityState<T>>(key: string, state: S): S;
5556
removeOne<S extends EntityState<T>>(key: number, state: S): S;

modules/entity/src/sorted_state_adapter.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ export function createSortedStateAdapter<T>(selectId: any, sort: any): any {
5050
return DidMutate.Both;
5151
}
5252

53+
function setOneMutably(entity: T, state: R): DidMutate;
54+
function setOneMutably(entity: any, state: any): DidMutate {
55+
const id = selectIdValue(entity, selectId);
56+
if (id in state.entities) {
57+
state.ids = state.ids.filter((val: string | number) => val !== id);
58+
merge([entity], state);
59+
return DidMutate.Both;
60+
} else {
61+
return addOneMutably(entity, state);
62+
}
63+
}
64+
5365
function updateOneMutably(update: Update<T>, state: R): DidMutate;
5466
function updateOneMutably(update: any, state: any): DidMutate {
5567
return updateManyMutably([update], state);
@@ -201,6 +213,7 @@ export function createSortedStateAdapter<T>(selectId: any, sort: any): any {
201213
upsertOne: createStateOperator(upsertOneMutably),
202214
addAll: createStateOperator(setAllMutably),
203215
setAll: createStateOperator(setAllMutably),
216+
setOne: createStateOperator(setOneMutably),
204217
addMany: createStateOperator(addManyMutably),
205218
updateMany: createStateOperator(updateManyMutably),
206219
upsertMany: createStateOperator(upsertManyMutably),

modules/entity/src/unsorted_state_adapter.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,21 @@ export function createUnsortedStateAdapter<T>(selectId: IdSelector<T>): any {
5050
return DidMutate.Both;
5151
}
5252

53+
function setOneMutably(entity: T, state: R): DidMutate;
54+
function setOneMutably(entity: any, state: any): DidMutate {
55+
const key = selectIdValue(entity, selectId);
56+
57+
if (key in state.entities) {
58+
state.entities[key] = entity;
59+
return DidMutate.EntitiesOnly;
60+
}
61+
62+
state.ids.push(key);
63+
state.entities[key] = entity;
64+
65+
return DidMutate.Both;
66+
}
67+
5368
function removeOneMutably(key: T, state: R): DidMutate;
5469
function removeOneMutably(key: any, state: any): DidMutate {
5570
return removeManyMutably([key], state);
@@ -196,6 +211,7 @@ export function createUnsortedStateAdapter<T>(selectId: IdSelector<T>): any {
196211
addMany: createStateOperator(addManyMutably),
197212
addAll: createStateOperator(setAllMutably),
198213
setAll: createStateOperator(setAllMutably),
214+
setOne: createStateOperator(setOneMutably),
199215
updateOne: createStateOperator(updateOneMutably),
200216
updateMany: createStateOperator(updateManyMutably),
201217
upsertOne: createStateOperator(upsertOneMutably),

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ state if no changes were made.
8686
- `addOne`: Add one entity to the collection
8787
- `addMany`: Add multiple entities to the collection
8888
- `addAll`: Replace current collection with provided collection
89+
- `setOne`: Add or Replace one entity in the collection
8990
- `removeOne`: Remove one entity from the collection
9091
- `removeMany`: Remove multiple entities from the collection, by id or by predicate
9192
- `removeAll`: Clear entity collection
@@ -112,6 +113,7 @@ import { User } from '../models/user.model';
112113

113114
export const loadUsers = createAction('[User/API] Load Users', props<{ users: User[] }>());
114115
export const addUser = createAction('[User/API] Add User', props<{ user: User }>());
116+
export const setUser = createAction('[User/API] Set User', props<{ user: User }>());
115117
export const upsertUser = createAction('[User/API] Upsert User', props<{ user: User }>());
116118
export const addUsers = createAction('[User/API] Add Users', props<{ users: User[] }>());
117119
export const upsertUsers = createAction('[User/API] Upsert Users', props<{ users: User[] }>());
@@ -148,6 +150,9 @@ const userReducer = createReducer(
148150
on(UserActions.addUser, (state, { user }) => {
149151
return adapter.addOne(user, state)
150152
}),
153+
on(UserActions.setUser, (state, { user }) => {
154+
return adapter.setOne(user, state)
155+
}),
151156
on(UserActions.upsertUser, (state, { user }) => {
152157
return adapter.upsertOne(user, state);
153158
}),

0 commit comments

Comments
 (0)