Skip to content

Commit

Permalink
feat(entity): add mapOne adapter method (#2628)
Browse files Browse the repository at this point in the history
Closes #2538
  • Loading branch information
timdeschryver committed Jul 24, 2020
1 parent bb309e0 commit d1891ad
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 21 deletions.
8 changes: 4 additions & 4 deletions .circleci/config.yml
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
23 changes: 23 additions & 0 deletions modules/entity/spec/sorted_state_adapter.spec.ts
Expand Up @@ -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({
Expand Down
23 changes: 23 additions & 0 deletions modules/entity/spec/unsorted_state_adapter.spec.ts
Expand Up @@ -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({
Expand Down
1 change: 1 addition & 0 deletions modules/entity/src/index.ts
Expand Up @@ -5,6 +5,7 @@ export {
EntityAdapter,
Update,
EntityMap,
EntityMapOne,
Predicate,
IdSelector,
Comparer,
Expand Down
13 changes: 13 additions & 0 deletions modules/entity/src/models.ts
Expand Up @@ -29,6 +29,18 @@ export type Predicate<T> = (entity: T) => boolean;

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

export interface EntityMapOneNum<T> {
id: number;
map: EntityMap<T>;
}

export interface EntityMapOneStr<T> {
id: string;
map: EntityMap<T>;
}

export type EntityMapOne<T> = EntityMapOneNum<T> | EntityMapOneStr<T>;

export interface EntityState<T> {
ids: string[] | number[];
entities: Dictionary<T>;
Expand Down Expand Up @@ -64,6 +76,7 @@ export interface EntityStateAdapter<T> {
upsertOne<S extends EntityState<T>>(entity: T, state: S): S;
upsertMany<S extends EntityState<T>>(entities: T[], state: S): S;

mapOne<S extends EntityState<T>>(map: EntityMapOne<T>, state: S): S;
map<S extends EntityState<T>>(map: EntityMap<T>, state: S): S;
}

Expand Down
21 changes: 21 additions & 0 deletions modules/entity/src/sorted_state_adapter.ts
Expand Up @@ -5,6 +5,8 @@ import {
EntityStateAdapter,
Update,
EntityMap,
EntityMapOneNum,
EntityMapOneStr,
} from './models';
import { createStateOperator, DidMutate } from './state_adapter';
import { createUnsortedStateAdapter } from './unsorted_state_adapter';
Expand Down Expand Up @@ -135,6 +137,24 @@ export function createSortedStateAdapter<T>(selectId: any, sort: any): any {
return updateManyMutably(updates, state);
}

function mapOneMutably(map: EntityMapOneNum<T>, state: R): DidMutate;
function mapOneMutably(map: EntityMapOneStr<T>, 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);
Expand Down Expand Up @@ -218,5 +238,6 @@ export function createSortedStateAdapter<T>(selectId: any, sort: any): any {
updateMany: createStateOperator(updateManyMutably),
upsertMany: createStateOperator(upsertManyMutably),
map: createStateOperator(mapMutably),
mapOne: createStateOperator(mapOneMutably),
};
}
21 changes: 21 additions & 0 deletions modules/entity/src/unsorted_state_adapter.ts
Expand Up @@ -5,6 +5,8 @@ import {
Update,
Predicate,
EntityMap,
EntityMapOneNum,
EntityMapOneStr,
} from './models';
import { createStateOperator, DidMutate } from './state_adapter';
import { selectIdValue } from './utils';
Expand Down Expand Up @@ -172,6 +174,24 @@ export function createUnsortedStateAdapter<T>(selectId: IdSelector<T>): any {
return updateManyMutably(updates, state);
}

function mapOneMutably(map: EntityMapOneNum<T>, state: R): DidMutate;
function mapOneMutably(map: EntityMapOneStr<T>, 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);
Expand Down Expand Up @@ -220,5 +240,6 @@ export function createUnsortedStateAdapter<T>(selectId: IdSelector<T>): any {
removeOne: createStateOperator(removeOneMutably),
removeMany: createStateOperator(removeManyMutably),
map: createStateOperator(mapMutably),
mapOne: createStateOperator(mapOneMutably),
};
}
25 changes: 17 additions & 8 deletions 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": {
Expand Down Expand Up @@ -47,7 +47,9 @@
"output": "/assets/js"
}
],
"styles": ["src/styles.scss"],
"styles": [
"src/styles.scss"
],
"scripts": []
},
"configurations": {
Expand Down Expand Up @@ -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",
Expand All @@ -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": []
}
}
Expand All @@ -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": []
}
}
Expand All @@ -179,4 +188,4 @@
"prefix": "aio"
}
}
}
}
23 changes: 14 additions & 9 deletions projects/ngrx.io/content/guide/entity/adapter.md
Expand Up @@ -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:

Expand All @@ -107,7 +108,7 @@ export interface User {

<code-example header="user.actions.ts">
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';

Expand All @@ -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&lt;User&gt; }>());
export const updateUsers = createAction('[User/API] Update Users', props<{ updates: Update&lt;User&gt;[] }>());
export const mapUser = createAction('[User/API] Map User', props<{ entityMap: EntityMapOne&lt;User&gt; }>());
export const mapUsers = createAction('[User/API] Map Users', props<{ entityMap: EntityMap&lt;User&gt; }>());
export const deleteUser = createAction('[User/API] Delete User', props<{ id: string }>());
export const deleteUsers = createAction('[User/API] Delete Users', props<{ ids: string[] }>());
Expand Down Expand Up @@ -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);
}),
Expand Down

0 comments on commit d1891ad

Please sign in to comment.