Skip to content

Commit

Permalink
chore(Example): Documented @ngrx/entity and added to example app (#328)
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonroberts authored and MikeRyanDev committed Sep 9, 2017
1 parent 0528d2d commit 0f00779
Show file tree
Hide file tree
Showing 13 changed files with 599 additions and 249 deletions.
24 changes: 24 additions & 0 deletions docs/entity/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# @ngrx/entity

Entity State adapter for managing record collections.

@ngrx/entity provides an API to manipulate and query entity collections.

- Reduces boilerplate for managing common datasets
- Provides performant operations for managing entity collections
- Extensible type-safe adapters for selecting entity information

### Installation
Install @ngrx/entity from npm:

`npm install @ngrx/entity --save` OR `yarn add @ngrx/entity`


### Nightly builds

`npm install github:ngrx/entity-builds` OR `yarn add github:ngrx/entity-builds`

## API Documentation
- [Interfaces](./interfaces.md)
- [Entity Adapter](./adapter.md)
- [Selectors]('./adapter.md#entity-selectors)
297 changes: 297 additions & 0 deletions docs/entity/adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
# Entity Adapter

## createEntityAdapter<T>

A method for returning a generic entity adapter for a single entity state collection. The
returned adapter provides many [methods](#adapter-methods) for performing operations
against the collection type. The method takes an object for configuration with 2 properties.

- `selectId`: A `method` for selecting the primary id for the collection
- `sort`: A [sort function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) for sorting the collection. Set to `false` to leave collection unsorted.

Usage:

```ts
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';

export interface User {
id: number;
name: string;
description: string;
}

export interface State extends EntityState<User> {
// additional entities state properties
selectedUserId: number;
}

export function sortByName(a: User, b: User): number {
return a.name.localeCompare(b.name);
}

export const adapter: EntityAdapter<User> = createEntityAdapter<User>({
selectId: (user: User) => user.id,
sort: sortByName,
});
```

## Adapter Methods

These methods are provided by the adapter object returned
when using [createEntityAdapter](#createEntityAdapter). The methods are used inside your reducer function to manage
the entity collection based on your provided actions.

### getInitialState

Returns the `initialState` for entity state based on the provided type. Additional state is also provided through the provided configuration object. The initialState is provided to your reducer function.

Usage:

```ts
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';

export interface User {
id: number;
name: string;
description: string;
}

export interface State extends EntityState<User> {
// additional entities state properties
selectedUserId: number | null;
}

export const initialState: State = adapter.getInitialState({
// additional entity state properties
selectedUserId: null
});

export function reducer(state = initialState, action): State {
switch(action.type) {
default: {
return state;
}
}
}
```

## Adapter Collection Methods

The entity adapter also provides methods for operations against an entity. These methods can change
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
* `addAll`: Replace current collection with provided collection
* `removeOne`: Remove one entity to the collection
* `removeMany`: Remove multiple entities to the collection
* `removeAll`: Clear entity collection
* `updateOne`: Update one entity in the collection
* `updateMany`: Update multiple entities in the collection

Usage:

`user.model.ts`

```ts
export interface User {
id: number;
name: string;
description: string;
}
```

`user.actions.ts`

```ts
import { Action } from '@ngrx/action';
import { User } from './user.model';

export const LOAD_USERS = '[User] Load Users';
export const ADD_USER = '[User] Add User';
export const ADD_USERS = '[User] Add Users';
export const UPDATE_USER = '[User] Update User';
export const UPDATE_USERS = '[User] Update Users';
export const DELETE_USER = '[User] Delete User';
export const DELETE_USERS = '[User] Delete Users';
export const CLEAR_USERS = '[User] Clear Users';

export class LoadUsers implements Action {
readonly type = LOAD_USERS;

constructor(public payload: { users: User[] }) {}
}

export class AddUser implements Action {
readonly type = ADD_USER;

constructor(public payload: { user: User }) {}
}

export class AddUsers implements Action {
readonly type = ADD_USERS;

constructor(public payload: { users: User[] }) {}
}

export class UpdateUser implements Action {
readonly type = UPDATE_USER;

constructor(public payload: { user: User }) {}
}

export class UpdateUsers implements Action {
readonly type = UPDATE_USERS;

constructor(public payload: { users: User[] }) {}
}

export class DeleteUser implements Action {
readonly type = DELETE_USER;

constructor(public payload: { user: User }) {}
}

export class DeleteUsers implements Action {
readonly type = DELETE_USERS;

constructor(public payload: { users: User[] }) {}
}

export class ClearUsers implements Action {
readonly type = CLEAR_USERS;
}

export type All =
LoadUsers
| AddUser
| AddUsers
| UpdateUser
| UpdateUsers
| DeleteUser
| DeleteUsers
| ClearUsers;
```

`user.reducer.ts`
```ts
import * as user from './user.actions';

export interface State extends EntityState<User> {
// additional entities state properties
selectedUserId: number | null;
}

export const adapter: EntityAdapter<User> = createEntityAdapter<User>({
selectId: (user: User) => user.id,
sort: true,
});

export const initialState: State = adapter.getInitialState({
// additional entity state properties
selectedUserId: null
});

export function reducer(
state = initialState,
action: UserActions.All
): State {
switch (action.type) {
case user.ADD_USER: {
return {
...state,
...adapter.addOne(action.payload.user, state),
};
}

case user.ADD_USERS: {
return {
...state,
...adapter.addMany(action.payload.users, state),
};
}

case user.UPDATE_USER: {
return {
...state,
...adapter.updateOne(action.payload.user, state),
};
}

case user.UPDATE_USERS: {
return {
...state,
...adapter.updateMany(action.payload.users, state),
};
}

case user.LOAD_USERS: {
return {
...state,
...adapter.addAll(action.payload.users, state),
};
}

case user.CLEAR_USERS: {
return {
...adapter.removeAll(state),
selectedUserId: null
};
}

default: {
return state;
}
}
}

export const getSelectedUserId = (state: State) => state.selectedUserId;
```

### Entity Selectors

The `getSelectors` method returned by the created entity adapter provides functions for selecting information from the entity.

The `getSelectors` method takes a selector function
as its only argument to select the piece of state for a defined entity.

Usage:

`reducers/index.ts`

```ts
import { createSelector, createFeatureSelector } from '@ngrx/store';
import * as fromUser from './user.reducer';

export interface State {
users: fromUser.State;
}

export const selectUserState = createFeatureSelector<fromUser.State>('users');

export const {
// select the array of user ids
selectIds: getUserIds,

// select the dictionary of user entities
selectEntities: getUserEntities,

// select the array of users
selectAll: getAllUsers,

// select the total user count
selectTotal: getUserTotal
} = fromUser.adapter.getSelectors(selectUserState);

export const selectUserIds = createSelector(selectUserState, getUserIds);
export const selectUserEntities = createSelector(selectUserState, getUserEntities);
export const selectAllUsers = createSelector(selectUserState, getAllUsers);
export const selectUserCount = createSelector(selectUserState, getUserTotal);
export const selectCurrentUserId = createSelector(selectUserState, fromUser.getSelectedUserId);
export const selectCurrentUser = createSelector(
selectUserEntities,
selectCurrentUserId,
(userEntities, userId) => userEntities[userId]
);
```
37 changes: 37 additions & 0 deletions docs/entity/interfaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Entity Interfaces

## EntityState<T>

The Entity State is a predefined generic interface for a given entity collection with the following properties:

* `ids`: An array of all the primary ids in the collection
* `entities`: A dictionary of entities in the collection indexed by the primary id

Extend this interface to provided any additional properties for the entity state.

Usage:

```ts
export interface User {
id: number;
name: string;
description: string;
}

export interface State extends EntityState<User> {
// additional entity state properties
selectedUserId: string | null;
}
```

## EntityAdapter<T>

Provides a generic type interface for the provided [entity adapter](./adapter#createEntityAdapter). The entity adapter provides many [collection methods](./adapter.md#adapter-collection-methods) for managing the entity state.

Usage:

```ts
export const adapter: EntityAdapter<User> = createEntityAdapter<User>({
selectId: (user: User) => user.id
});
```
20 changes: 20 additions & 0 deletions example-app/app/books/models/book.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,23 @@ export interface Book {
};
};
}

export function generateMockBook(): Book {
return {
id: '1',
volumeInfo: {
title: 'title',
subtitle: 'subtitle',
authors: ['author'],
publisher: 'publisher',
publishDate: '',
description: 'description',
averageRating: 3,
ratingsCount: 5,
imageLinks: {
thumbnail: 'string',
smallThumbnail: 'string',
},
},
};
}
Loading

0 comments on commit 0f00779

Please sign in to comment.