NGRX

Neil HaddleyNovember 27, 2022

haddley-ngrx

NgRx is inspired by the Redux pattern.

NgRx stores a single state and uses actions to express state changes.

NGRX state management lifecycle

NGRX state management lifecycle

I installed the Angular command line interface using npm

BASH
1% npm i -g @angular/cli

I created a new Angular project using "ng new"

BASH
1% ng new haddley-ngrx
ng new haddley-ngrx

ng new haddley-ngrx

State

The state of the haddley-ngrx application is a number between 1 and 6.

The app component template displays the "value"

The app component template displays the "value"

The app component class sets the "value"

The app component class sets the "value"

The app component style ensures the value is big enough to see at a distance

The app component style ensures the value is big enough to see at a distance

A random number between 1 and 6.

A random number between 1 and 6.

(click)

Every time a user refreshes the haddley-ngrx app a random number is displayed.

I updated the code so that the number is updated in response to a mouse click.

I added (click)="updateValue()" to app component template

I added (click)="updateValue()" to app component template

I added code to update "this.value" to app component class

I added code to update "this.value" to app component class

I refactored the code

I refactored the code

I created a "valueBetween1And6" function

I created a "valueBetween1And6" function

I updated the declaration of value to use the "valueBetween1And6" function

I updated the declaration of value to use the "valueBetween1And6" function

ng test

I ran "ng test" to check that the haddley-ngrx application was working properly.

Two tests passed and one test failed.

ng test

ng test

karma page

karma page

'haddley-ngrx app is running'?

'haddley-ngrx app is running'?

Updating the tests

In this case the haddley-ngrx app is working fine and the test is wrong.

I want the application to display a value between 1 and 6.

I don't want the application to display 'haddley-ngrx app is running'.

So I updated the last test.

app component should render a number between 1 and 6

app component should render a number between 1 and 6

ng test

ng test

Moving to NGRX

Using NGRX I moved "value" out of the app component and into a store.

I updated the app component to dispatch an Action in response to the (click) and to refresh the page content in response to a store Selector.

Folders

I created a src/app/state folder and a src/app/state/dice folder.

dice.actions

I created a dice actions typescript file and defined three actions: {roll, rollSuccess, and rollFailure}

The app component will dispatch a "roll" Action in response to a (click).

The app component will not dispatch the rollSuccess Action or the rollFailure Action.

npm i @ngrx/store

npm i @ngrx/store

dice.state

I created a dice state typescript file and defined a DiceState interface.

NGRX should not throw an exception in response to the app component dispatching an Action.

Instead a Selector will return a result (to the component) indicating success or failure.

For completeness I have included a (nullable) error value in the DiceState interface.

DiceState interface

DiceState interface

dice.reducer

I created a dice reducer typescript file to specify how Actions will be processed.

The rollSuccess action updates the state.value to the number passed to it as a property.

The rollFailure action updates the state.error to the text passed to it as a property.

It might seem odd that the roll Action updates the state.error but does not update the state.value (see Effects below).

diceReducer

diceReducer

app.state

As my application grows it is likely that I will add features that are unrelated to dice.state.

With that in mind I have created an app.state interface that can be used in the future to access dice.state and state related to other features.

I added an app.state typescript file to the state folder.

app.state interface

app.state interface

dice.selectors

The app.component will access the dice state using selectors.

I added the dice.selectors typescript file and created the two selectors the app.component needs.

dice.selectors

dice.selectors

app.module

To get NGRX to work I needed to add two modules to the app.module file.

First I added StoreModule

adding StoreModule to app.module

adding StoreModule to app.module

Using the selectors

I updated app component template and app component class to make use of the selectDiceValue and selectDiceError selectors.

app component class

app component class

app component template

app component template

ng serve -o(click) seems to do nothingrefreshing the page seems to work

ng serve -o(click) seems to do nothingrefreshing the page seems to work

ng test

After these updates the tests fail.

No provider for Store!

No provider for Store!

MockStore

To get the ng tests to pass we need to add a "mock store"

provideMockStore

provideMockStore

ng test success

ng test success

dice.effects

When the app component dispatches the "roll" Action the app component does not provide a value.

Without additional effort the roll Action is not in a position to update the state.

NGRX effects address this problem.

I created a DiceEffects class that responds to "roll" Actions.

When the DiceEffects class spots/notices/is notified of a roll Action it calculates a random value between 1 and 6 and then (some moments later) dispatches a corresponding rollSuccess or rollFailure Action.

dice.effects

dice.effects

EffectsModule import added to AppModule

EffectsModule import added to AppModule

Final result

The final result is an Angular application that uses NGRX for state management.

ng test

ng test

ng serve -o

ng serve -o