Skip to content

Commit 2be4bda

Browse files
docs: refactor books to createFeature
1 parent a1f5329 commit 2be4bda

File tree

6 files changed

+158
-245
lines changed

6 files changed

+158
-245
lines changed

projects/example-app/src/app/books/books.routes.ts

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,12 @@ import {
66
ViewBookPageComponent,
77
} from '@example-app/books/containers';
88
import { bookExistsGuard } from '@example-app/books/guards';
9-
import { provideState } from '@ngrx/store';
10-
import * as fromBooks from '@example-app/books/reducers';
11-
import { provideEffects } from '@ngrx/effects';
12-
import { BookEffects, CollectionEffects } from './effects';
9+
import { provideBooks } from '@example-app/books/reducers';
1310

1411
export const BOOKS_ROUTES: Routes = [
1512
{
1613
path: '',
17-
providers: [
18-
/**
19-
* provideState() is used for composing state
20-
* from feature modules. These modules can be loaded
21-
* eagerly or lazily and will be dynamically added to
22-
* the existing state.
23-
*/
24-
provideState(fromBooks.booksFeatureKey, fromBooks.reducers),
25-
/**
26-
* provideEffects() is used to register effects
27-
* from feature modules. Effects can be loaded
28-
* eagerly or lazily and will be started immediately.
29-
*
30-
* All Effects will only be instantiated once regardless of
31-
* whether they are registered once or multiple times.
32-
*/
33-
provideEffects(BookEffects, CollectionEffects),
34-
],
14+
providers: [provideBooks()],
3515
children: [
3616
{
3717
path: 'find',

projects/example-app/src/app/books/reducers/books.reducer.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { reducer } from '@example-app/books/reducers/books.reducer';
21
import * as fromBooks from '@example-app/books/reducers/books.reducer';
32
import { BooksApiActions } from '@example-app/books/actions/books-api.actions';
43
import { BookActions } from '@example-app/books/actions/book.actions';
54
import { CollectionApiActions } from '@example-app/books/actions/collection-api.actions';
65
import { ViewBookPageActions } from '@example-app/books/actions/view-book-page.actions';
76
import { Book, generateMockBook } from '@example-app/books/models';
7+
import { booksFeature } from '@example-app/books/reducers/books.reducer';
88

99
describe('BooksReducer', () => {
1010
const book1 = generateMockBook();
@@ -21,7 +21,7 @@ describe('BooksReducer', () => {
2121

2222
describe('undefined action', () => {
2323
it('should return the default state', () => {
24-
const result = reducer(undefined, {} as any);
24+
const result = booksFeature.reducer(undefined, {} as any);
2525

2626
expect(result).toMatchSnapshot();
2727
});
@@ -38,7 +38,7 @@ describe('BooksReducer', () => {
3838
) {
3939
const createAction = action({ books });
4040

41-
const result = reducer(booksInitialState, createAction);
41+
const result = booksFeature.reducer(booksInitialState, createAction);
4242

4343
expect(result).toMatchSnapshot();
4444
}
@@ -52,7 +52,7 @@ describe('BooksReducer', () => {
5252
const differentBook2 = { ...books[0], foo: 'bar' };
5353
const createAction = action({ books: [books[1], differentBook2] });
5454

55-
const result = reducer(booksInitialState, createAction);
55+
const result = booksFeature.reducer(booksInitialState, createAction);
5656

5757
expect(result).toMatchSnapshot();
5858
}
@@ -94,15 +94,15 @@ describe('BooksReducer', () => {
9494
it('should add a single book, if the book does not exist', () => {
9595
const action = BookActions.loadBook({ book: book1 });
9696

97-
const result = reducer(fromBooks.initialState, action);
97+
const result = booksFeature.reducer(fromBooks.initialState, action);
9898

9999
expect(result).toMatchSnapshot();
100100
});
101101

102102
it('should return the existing state if the book exists', () => {
103103
const action = BookActions.loadBook({ book: book1 });
104104

105-
const result = reducer(expectedResult, action);
105+
const result = booksFeature.reducer(expectedResult, action);
106106

107107
expect(result).toMatchSnapshot();
108108
});
@@ -112,7 +112,7 @@ describe('BooksReducer', () => {
112112
it('should set the selected book id on the state', () => {
113113
const action = ViewBookPageActions.selectBook({ id: book1.id });
114114

115-
const result = reducer(initialState, action);
115+
const result = booksFeature.reducer(initialState, action);
116116

117117
expect(result).toMatchSnapshot();
118118
});
@@ -121,7 +121,7 @@ describe('BooksReducer', () => {
121121
describe('Selectors', () => {
122122
describe('selectId', () => {
123123
it('should return the selected id', () => {
124-
const result = fromBooks.selectId({
124+
const result = booksFeature.selectSelectedBookId.projector({
125125
...initialState,
126126
selectedBookId: book1.id,
127127
});
Lines changed: 30 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import { createEntityAdapter, EntityAdapter, EntityState } from '@ngrx/entity';
2-
import { createReducer, on } from '@ngrx/store';
2+
import { createFeature, createReducer, on } from '@ngrx/store';
33

44
import { BooksApiActions } from '@example-app/books/actions/books-api.actions';
55
import { BookActions } from '@example-app/books/actions/book.actions';
66
import { CollectionApiActions } from '@example-app/books/actions/collection-api.actions';
77
import { ViewBookPageActions } from '@example-app/books/actions/view-book-page.actions';
88
import { Book } from '@example-app/books/models';
99

10-
export const booksFeatureKey = 'books';
11-
1210
/**
1311
* @ngrx/entity provides a predefined interface for handling
1412
* a structured dictionary of records. This interface
@@ -42,41 +40,33 @@ export const initialState: State = adapter.getInitialState({
4240
selectedBookId: null,
4341
});
4442

45-
export const reducer = createReducer(
46-
initialState,
47-
/**
48-
* The addMany function provided by the created adapter
49-
* adds many records to the entity dictionary
50-
* and returns a new state including those records. If
51-
* the collection is to be sorted, the adapter will
52-
* sort each record upon entry into the sorted array.
53-
*/
54-
on(
55-
BooksApiActions.searchSuccess,
56-
CollectionApiActions.loadBooksSuccess,
57-
(state, { books }) => adapter.addMany(books, state)
43+
export const booksFeature = createFeature({
44+
name: 'books',
45+
reducer: createReducer(
46+
initialState,
47+
/**
48+
* The addMany function provided by the created adapter
49+
* adds many records to the entity dictionary
50+
* and returns a new state including those records. If
51+
* the collection is to be sorted, the adapter will
52+
* sort each record upon entry into the sorted array.
53+
*/
54+
on(
55+
BooksApiActions.searchSuccess,
56+
CollectionApiActions.loadBooksSuccess,
57+
(state, { books }) => adapter.addMany(books, state)
58+
),
59+
/**
60+
* The addOne function provided by the created adapter
61+
* adds one record to the entity dictionary
62+
* and returns a new state including that records if it doesn't
63+
* exist already. If the collection is to be sorted, the adapter will
64+
* insert the new record into the sorted array.
65+
*/
66+
on(BookActions.loadBook, (state, { book }) => adapter.addOne(book, state)),
67+
on(ViewBookPageActions.selectBook, (state, { id }) => ({
68+
...state,
69+
selectedBookId: id,
70+
}))
5871
),
59-
/**
60-
* The addOne function provided by the created adapter
61-
* adds one record to the entity dictionary
62-
* and returns a new state including that records if it doesn't
63-
* exist already. If the collection is to be sorted, the adapter will
64-
* insert the new record into the sorted array.
65-
*/
66-
on(BookActions.loadBook, (state, { book }) => adapter.addOne(book, state)),
67-
on(ViewBookPageActions.selectBook, (state, { id }) => ({
68-
...state,
69-
selectedBookId: id,
70-
}))
71-
);
72-
73-
/**
74-
* Because the data structure is defined within the reducer it is optimal to
75-
* locate our selector functions at this level. If store is to be thought of
76-
* as a database, and reducers the tables, selectors can be considered the
77-
* queries into said database. Remember to keep your selectors small and
78-
* focused so they can be combined and composed to fit each particular
79-
* use-case.
80-
*/
81-
82-
export const selectId = (state: State) => state.selectedBookId;
72+
});
Lines changed: 48 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { createReducer, on } from '@ngrx/store';
1+
import { createFeature, createReducer, on } from '@ngrx/store';
22

33
import { CollectionApiActions } from '@example-app/books/actions/collection-api.actions';
44
import { CollectionPageActions } from '@example-app/books/actions/collection-page.actions';
55
import { SelectedBookPageActions } from '@example-app/books/actions/selected-book-page.actions';
66

7-
export const collectionFeatureKey = 'collection';
8-
97
export interface State {
108
loaded: boolean;
119
loading: boolean;
@@ -18,53 +16,56 @@ const initialState: State = {
1816
ids: [],
1917
};
2018

21-
export const reducer = createReducer(
22-
initialState,
23-
on(CollectionPageActions.enter, (state) => ({
24-
...state,
25-
loading: true,
26-
})),
27-
on(CollectionApiActions.loadBooksSuccess, (_state, { books }) => ({
28-
loaded: true,
29-
loading: false,
30-
ids: books.map((book) => book.id),
31-
})),
32-
/**
33-
* Optimistically add book to collection.
34-
* If this succeeds there's nothing to do.
35-
* If this fails we revert state by removing the book.
36-
*
37-
* `on` supports handling multiple types of actions
38-
*/
39-
on(
40-
SelectedBookPageActions.addBook,
41-
CollectionApiActions.removeBookFailure,
42-
(state, { book }) => {
43-
if (state.ids.indexOf(book.id) > -1) {
44-
return state;
19+
export const collectionFeature = createFeature({
20+
name: 'booksCollection',
21+
reducer: createReducer(
22+
initialState,
23+
on(CollectionPageActions.enter, (state) => ({
24+
...state,
25+
loading: true,
26+
})),
27+
on(CollectionApiActions.loadBooksSuccess, (_state, { books }) => ({
28+
loaded: true,
29+
loading: false,
30+
ids: books.map((book) => book.id),
31+
})),
32+
/**
33+
* Optimistically add book to collection.
34+
* If this succeeds there's nothing to do.
35+
* If this fails we revert state by removing the book.
36+
*
37+
* `on` supports handling multiple types of actions
38+
*/
39+
on(
40+
SelectedBookPageActions.addBook,
41+
CollectionApiActions.removeBookFailure,
42+
(state, { book }) => {
43+
if (state.ids.indexOf(book.id) > -1) {
44+
return state;
45+
}
46+
return {
47+
...state,
48+
ids: [...state.ids, book.id],
49+
};
4550
}
46-
return {
51+
),
52+
/**
53+
* Optimistically remove book from collection.
54+
* If addBook fails, we "undo" adding the book.
55+
*/
56+
on(
57+
SelectedBookPageActions.removeBook,
58+
CollectionApiActions.addBookFailure,
59+
(state, { book }) => ({
4760
...state,
48-
ids: [...state.ids, book.id],
49-
};
50-
}
61+
ids: state.ids.filter((id) => id !== book.id),
62+
})
63+
)
5164
),
52-
/**
53-
* Optimistically remove book from collection.
54-
* If addBook fails, we "undo" adding the book.
55-
*/
56-
on(
57-
SelectedBookPageActions.removeBook,
58-
CollectionApiActions.addBookFailure,
59-
(state, { book }) => ({
60-
...state,
61-
ids: state.ids.filter((id) => id !== book.id),
62-
})
63-
)
64-
);
65+
});
6566

66-
export const getLoaded = (state: State) => state.loaded;
67+
export const getLoaded = collectionFeature.selectLoaded;
6768

68-
export const getLoading = (state: State) => state.loading;
69+
export const getLoading = collectionFeature.selectLoading;
6970

70-
export const getIds = (state: State) => state.ids;
71+
export const getIds = collectionFeature.selectIds;

0 commit comments

Comments
 (0)