Skip to content

Commit 9bdfd70

Browse files
MikeRyanDevbrandonroberts
authored andcommitted
feat(Platform): Introduce @ngrx/entity (#207)
1 parent c490bc9 commit 9bdfd70

22 files changed

+1032
-58
lines changed

build/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,8 @@ export const packages: PackageDescription[] = [
2525
name: 'store-devtools',
2626
hasTestingModule: false,
2727
},
28+
{
29+
name: 'entity',
30+
hasTestingModule: false,
31+
},
2832
];

modules/entity/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@ngrx/entity
2+
=======
3+
4+
The sources for this package are in the main [ngrx/platform](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.
5+
6+
License: MIT

modules/entity/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* DO NOT EDIT
3+
*
4+
* This file is automatically generated at build
5+
*/
6+
7+
export * from './public_api';

modules/entity/package.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "@ngrx/entity",
3+
"version": "4.0.1",
4+
"description": "Common utilities for entity reducers",
5+
"module": "@ngrx/entity.es5.js",
6+
"es2015": "@ngrx/entity.js",
7+
"main": "bundles/entity.umd.js",
8+
"typings": "entity.d.ts",
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/ngrx/platform.git"
12+
},
13+
"authors": [
14+
"Mike Ryan"
15+
],
16+
"license": "MIT",
17+
"peerDependencies": {
18+
"@angular/core": "^4.0.0",
19+
"@ngrx/store": "^4.0.0",
20+
"rxjs": "^5.0.0"
21+
}
22+
}

modules/entity/public_api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './src/index';

modules/entity/rollup.config.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export default {
2+
entry: './dist/entity/@ngrx/entity.es5.js',
3+
dest: './dist/entity/bundles/entity.umd.js',
4+
format: 'umd',
5+
exports: 'named',
6+
moduleName: 'ngrx.entity',
7+
globals: {
8+
'@ngrx/store': 'ngrx.store'
9+
}
10+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { createEntityAdapter, EntityAdapter } from '../src';
2+
import { BookModel } from './fixtures/book';
3+
4+
describe('Entity State', () => {
5+
let adapter: EntityAdapter<BookModel>;
6+
7+
beforeEach(() => {
8+
adapter = createEntityAdapter({
9+
selectId: (book: BookModel) => book.id,
10+
});
11+
});
12+
13+
it('should let you get the initial state', () => {
14+
const initialState = adapter.getInitialState();
15+
16+
expect(initialState).toEqual({
17+
ids: [],
18+
entities: {},
19+
});
20+
});
21+
22+
it('should let you provide additional initial state properties', () => {
23+
const additionalProperties = { isHydrated: true };
24+
25+
const initialState = adapter.getInitialState(additionalProperties);
26+
27+
expect(initialState).toEqual({
28+
...additionalProperties,
29+
ids: [],
30+
entities: {},
31+
});
32+
});
33+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const deepFreeze = require('deep-freeze');
2+
3+
export interface BookModel {
4+
id: string;
5+
title: string;
6+
}
7+
8+
export const AClockworkOrange: BookModel = deepFreeze({
9+
id: 'aco',
10+
title: 'A Clockwork Orange',
11+
});
12+
13+
export const AnimalFarm: BookModel = deepFreeze({
14+
id: 'af',
15+
title: 'Animal Farm',
16+
});
17+
18+
export const TheGreatGatsby: BookModel = deepFreeze({
19+
id: 'tgg',
20+
title: 'The Great Gatsby',
21+
});
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
import { EntityStateAdapter, EntityState } from '../src/models';
2+
import { createEntityAdapter } from '../src/create_adapter';
3+
import {
4+
BookModel,
5+
TheGreatGatsby,
6+
AClockworkOrange,
7+
AnimalFarm,
8+
} from './fixtures/book';
9+
10+
describe('Sorted State Adapter', () => {
11+
let adapter: EntityStateAdapter<BookModel>;
12+
let state: EntityState<BookModel>;
13+
14+
beforeEach(() => {
15+
adapter = createEntityAdapter({
16+
selectId: (book: BookModel) => book.id,
17+
sort: (a, b) => a.title.localeCompare(b.title),
18+
});
19+
20+
state = { ids: [], entities: {} };
21+
});
22+
23+
it('should let you add one entity to the state', () => {
24+
const withOneEntity = adapter.addOne(TheGreatGatsby, state);
25+
26+
expect(withOneEntity).toEqual({
27+
ids: [TheGreatGatsby.id],
28+
entities: {
29+
[TheGreatGatsby.id]: TheGreatGatsby,
30+
},
31+
});
32+
});
33+
34+
it('should not change state if you attempt to re-add an entity', () => {
35+
const withOneEntity = adapter.addOne(TheGreatGatsby, state);
36+
37+
const readded = adapter.addOne(TheGreatGatsby, withOneEntity);
38+
39+
expect(readded).toEqual(withOneEntity);
40+
});
41+
42+
it('should let you add many entities to the state', () => {
43+
const withOneEntity = adapter.addOne(TheGreatGatsby, state);
44+
45+
const withManyMore = adapter.addMany(
46+
[AClockworkOrange, AnimalFarm],
47+
withOneEntity
48+
);
49+
50+
expect(withManyMore).toEqual({
51+
ids: [AClockworkOrange.id, AnimalFarm.id, TheGreatGatsby.id],
52+
entities: {
53+
[TheGreatGatsby.id]: TheGreatGatsby,
54+
[AClockworkOrange.id]: AClockworkOrange,
55+
[AnimalFarm.id]: AnimalFarm,
56+
},
57+
});
58+
});
59+
60+
it('should let you add all entities to the state', () => {
61+
const withOneEntity = adapter.addOne(TheGreatGatsby, state);
62+
63+
const withAll = adapter.addAll(
64+
[AClockworkOrange, AnimalFarm],
65+
withOneEntity
66+
);
67+
68+
expect(withAll).toEqual({
69+
ids: [AClockworkOrange.id, AnimalFarm.id],
70+
entities: {
71+
[AClockworkOrange.id]: AClockworkOrange,
72+
[AnimalFarm.id]: AnimalFarm,
73+
},
74+
});
75+
});
76+
77+
it('should let you add remove an entity from the state', () => {
78+
const withOneEntity = adapter.addOne(TheGreatGatsby, state);
79+
80+
const withoutOne = adapter.removeOne(TheGreatGatsby.id, state);
81+
82+
expect(withoutOne).toEqual({
83+
ids: [],
84+
entities: {},
85+
});
86+
});
87+
88+
it('should let you remove many entities from the state', () => {
89+
const withAll = adapter.addAll(
90+
[TheGreatGatsby, AClockworkOrange, AnimalFarm],
91+
state
92+
);
93+
94+
const withoutMany = adapter.removeMany(
95+
[TheGreatGatsby.id, AClockworkOrange.id],
96+
withAll
97+
);
98+
99+
expect(withoutMany).toEqual({
100+
ids: [AnimalFarm.id],
101+
entities: {
102+
[AnimalFarm.id]: AnimalFarm,
103+
},
104+
});
105+
});
106+
107+
it('should let you remove all entities from the state', () => {
108+
const withAll = adapter.addAll(
109+
[TheGreatGatsby, AClockworkOrange, AnimalFarm],
110+
state
111+
);
112+
113+
const withoutAll = adapter.removeAll(withAll);
114+
115+
expect(withoutAll).toEqual({
116+
ids: [],
117+
entities: {},
118+
});
119+
});
120+
121+
it('should let you update an entity in the state', () => {
122+
const withOne = adapter.addOne(TheGreatGatsby, state);
123+
const changes = { title: 'A New Hope' };
124+
125+
const withUpdates = adapter.updateOne(
126+
{
127+
id: TheGreatGatsby.id,
128+
changes,
129+
},
130+
withOne
131+
);
132+
133+
expect(withUpdates).toEqual({
134+
ids: [TheGreatGatsby.id],
135+
entities: {
136+
[TheGreatGatsby.id]: {
137+
...TheGreatGatsby,
138+
...changes,
139+
},
140+
},
141+
});
142+
});
143+
144+
it('should not change state if you attempt to update an entity that has not been added', () => {
145+
const withUpdates = adapter.updateOne(
146+
{
147+
id: TheGreatGatsby.id,
148+
changes: { title: 'A New Title' },
149+
},
150+
state
151+
);
152+
153+
expect(withUpdates).toEqual(state);
154+
});
155+
156+
it('should let you update the id of entity', () => {
157+
const withOne = adapter.addOne(TheGreatGatsby, state);
158+
const changes = { id: 'A New Id' };
159+
160+
const withUpdates = adapter.updateOne(
161+
{
162+
id: TheGreatGatsby.id,
163+
changes,
164+
},
165+
withOne
166+
);
167+
168+
expect(withUpdates).toEqual({
169+
ids: [changes.id],
170+
entities: {
171+
[changes.id]: {
172+
...TheGreatGatsby,
173+
...changes,
174+
},
175+
},
176+
});
177+
});
178+
179+
it('should resort correctly if the id and sort key update', () => {
180+
const withOne = adapter.addAll(
181+
[TheGreatGatsby, AnimalFarm, AClockworkOrange],
182+
state
183+
);
184+
const changes = { id: 'A New Id', title: AnimalFarm.title };
185+
186+
const withUpdates = adapter.updateOne(
187+
{
188+
id: TheGreatGatsby.id,
189+
changes,
190+
},
191+
withOne
192+
);
193+
194+
expect(withUpdates).toEqual({
195+
ids: [AClockworkOrange.id, changes.id, AnimalFarm.id],
196+
entities: {
197+
[AClockworkOrange.id]: AClockworkOrange,
198+
[changes.id]: {
199+
...TheGreatGatsby,
200+
...changes,
201+
},
202+
[AnimalFarm.id]: AnimalFarm,
203+
},
204+
});
205+
});
206+
207+
it('should let you update many entities in the state', () => {
208+
const firstChange = { title: 'Zack' };
209+
const secondChange = { title: 'Aaron' };
210+
const withMany = adapter.addAll([TheGreatGatsby, AClockworkOrange], state);
211+
212+
const withUpdates = adapter.updateMany(
213+
[
214+
{ id: TheGreatGatsby.id, changes: firstChange },
215+
{ id: AClockworkOrange.id, changes: secondChange },
216+
],
217+
withMany
218+
);
219+
220+
expect(withUpdates).toEqual({
221+
ids: [AClockworkOrange.id, TheGreatGatsby.id],
222+
entities: {
223+
[TheGreatGatsby.id]: {
224+
...TheGreatGatsby,
225+
...firstChange,
226+
},
227+
[AClockworkOrange.id]: {
228+
...AClockworkOrange,
229+
...secondChange,
230+
},
231+
},
232+
});
233+
});
234+
});

0 commit comments

Comments
 (0)