Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add exported reducer function to createReducer examples #1924

Merged
merged 1 commit into from
Jun 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions projects/ngrx.io/content/guide/entity/adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Returns the `initialState` for entity state based on the provided type. Addition
Usage:

<code-example header="user.reducer.ts">
import { createReducer } from '@ngrx/store';
import { Action, createReducer } from '@ngrx/store';
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';

export interface User {
Expand All @@ -70,7 +70,11 @@ export const initialState: State = adapter.getInitialState({
selectedUserId: null,
});

export const reducer = createReducer(initialState);
const userReducer = createReducer(initialState);

export function reducer(state: State | undefined: action: Action) {
return userReducer(state, action);
}
</code-example>

## Adapter Collection Methods
Expand Down Expand Up @@ -122,7 +126,7 @@ export const clearUsers = createAction('[User/API] Clear Users');
</code-example>

<code-example header="user.reducer.ts">
import { createReducer, on } from '@ngrx/store';
import { Action, createReducer, on } from '@ngrx/store';
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
import { User } from '../models/user.model';
import * as UserActions from '../actions/user.actions';
Expand All @@ -139,7 +143,7 @@ export const initialState: State = adapter.getInitialState({
selectedUserId: null,
});

export const reducer = createReducer(
const userReducer = createReducer(
initialState,
on(UserActions.addUser, (state, { user }) => {
return adapter.addOne(user, state)
Expand Down Expand Up @@ -176,7 +180,12 @@ export const reducer = createReducer(
}),
on(UserActions.clearUsers, state => {
return adapter.removeAll({ ...state, selectedUserId: null });
}));
})
);

export function reducer(state: State | undefined: action: Action) {
return userReducer(state, action);
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The entity adapter is only used to update the `EntityState` properties. The addi

<code-example header="user.reducer.ts">
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
import { createReducer, on } from '@ngrx/store';
import { Action, createReducer, on } from '@ngrx/store';
import { User } from '../models/user.model';
import * as UserActions from '../actions/user.actions';

Expand All @@ -56,7 +56,8 @@ export const initialState: State = adapter.getInitialState({
selectedUserId: null,
});

export const reducer = createReducer(initialState,
export const userReducer = createReducer(
initialState,
on(UserActions.selectUser, (state, { userId }) => {
return { ...state, selectedUserId: userId };
}),
Expand All @@ -65,4 +66,7 @@ export const reducer = createReducer(initialState,
})
);

export function reducer(state: State | undefined: action: Action) {
return userReducer(state, action);
}
</code-example>
37 changes: 27 additions & 10 deletions projects/ngrx.io/content/guide/store/reducers.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ There are a few consistent parts of every piece of state managed by a reducer.

- An interface or type that defines the shape of the state.
- The arguments including the initial state or current state and the current action.
- The switch statement
- The functions that handle state changes for their associated action(s).

Below is an example of a set of actions to handle the state of a scoreboard,
and the associated reducer function.
Expand All @@ -36,6 +36,7 @@ a shape for the piece of state.
Each reducer function is a listener of actions. The scoreboard actions defined above describe the possible transitions handled by the reducer. Import multiple sets of actions to handle additional state transitions within a reducer.

<code-example header="scoreboard.reducer.ts">
import { Action, createReducer, on } from '@ngrx/store';
import * as ScoreboardPageActions from '../actions/scoreboard-page.actions';

export interface State {
Expand Down Expand Up @@ -64,26 +65,42 @@ The initial values for the `home` and `away` properties of the state are 0.

### Creating the reducer function

The reducer function's responsibility is to handle the state transitions in an immutable way. Define a reducer function that handles the actions for managing the state of the scoreboard.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't highlight it but on line 15, we define the reducer function with - The switch statement

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

The reducer function's responsibility is to handle the state transitions in an immutable way. Create a reducer function that handles the actions for managing the state of the scoreboard using the `createReducer` function.

<code-example header="scoreboard.reducer.ts">
export const reducer = createReducer(
initialScoreState,
const scoreboardReducer = createReducer(
initialState,
on(ScoreboardPageActions.homeScore, state => ({ ...state, home: state.home + 1 })),
on(ScoreboardPageActions.awayScore, state => ({ ...state, away: state.away + 1 })),
on(ScoreboardPageActions.resetScore, state => ({ home: 0, away: 0 }))
);

export function reducer(state: State | undefined: action: Action) {
return scoreboardReducer(state, action);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will only work for StoreModule.forFeature or for ReducerActionMap. I think we should recommend combineReducers way if there are more than a single reducer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I think that should be an additional section to talk about combineReducers

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't found any. Is it somewhere I missed? :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maxime1992 there's an open issue - #1179

</code-example>

In the example above, the reducer is handling 3 actions: `[Scoreboard Page] Home Score`, `[Scoreboard Page] Away Score`, and `[Scoreboard Page] Reset`. Each action is strongly-typed. Each action handles the state transition immutably. This means that the state transitions are not modifying the original state, but are returning a new state object using the spread operator. The spread syntax copies the properties from the current state into the object, creating a new reference. This ensures that a new state is produced with each change, preserving the purity of the change. This also promotes referential integrity, guaranteeing that the old reference was discarded when a state change occurred.
<div class="alert is-important">

**Note:** The exported `reducer` function is necessary as [function calls are not supported](https://angular.io/guide/aot-compiler#function-calls-are-not-supported) by the AOT compiler.

</div>

In the example above, the reducer is handling 3 actions: `[Scoreboard Page] Home Score`, `[Scoreboard Page] Away Score`, and `[Scoreboard Page] Score Reset`. Each action is strongly-typed. Each action handles the state transition immutably. This means that the state transitions are not modifying the original state, but are returning a new state object using the spread operator. The spread syntax copies the properties from the current state into the object, creating a new reference. This ensures that a new state is produced with each change, preserving the purity of the change. This also promotes referential integrity, guaranteeing that the old reference was discarded when a state change occurred.

<div class="alert is-important">

**Note:** The [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) only does shallow copying and does not handle deeply nested objects. You need to copy each level in the object to ensure immutability. There are libraries that handle deep copying including [lodash](https://lodash.com) and [immer](https://github.com/mweststrate/immer).

</div>

When an action is dispatched, _all registered reducers_ receive the action. Whether they handle the action is determined by the switch statement. For this reason, each switch statement _always_ includes a default case that returns the previous state when the reducer function doesn't need to handle the action.
When an action is dispatched, _all registered reducers_ receive the action. Whether they handle the action is determined by the `on` functions that associate one or more actions with a given state change.

<div class="alert is-important">

**Note:** You can also write reducers using switch statements, which was the previously defined way before reducer creators were introduced in NgRx. If you are looking for examples of reducers using switch statements, visit the documentation for [versions 7.x and prior](https://v7.ngrx.io/guide/store/reducers).

</div>

## Registering root state

Expand All @@ -92,11 +109,11 @@ The state of your application is defined as one large object. Registering reduce
<code-example header="app.module.ts">
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { scoreboardReducer } from './reducers/scoreboard.reducer';
import * as fromScoreboard from './reducers/scoreboard.reducer';

@NgModule({
imports: [
StoreModule.forRoot({ game: scoreboardReducer })
StoreModule.forRoot({ game: fromScoreboard.reducer })
],
})
export class AppModule {}
Expand Down Expand Up @@ -134,11 +151,11 @@ Now use the `scoreboard` reducer with a feature `NgModule` named `ScoreboardModu
<code-example header="scoreboard.module.ts">
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { scoreboardReducer } from './reducers/scoreboard.reducer';
import * as fromScoreboard from './reducers/scoreboard.reducer';

@NgModule({
imports: [
StoreModule.forFeature('game', scoreboardReducer)
StoreModule.forFeature('game', fromScoreboard.reducer)
],
})
export class ScoreboardModule {}
Expand Down
3 changes: 1 addition & 2 deletions projects/ngrx.io/content/navigation.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@
"SideNav": [
{
"url": "docs",
"title": "Docs",
"hidden": true
"title": "Introduction"
},
{
"title": "@ngrx/store",
Expand Down