Angular State Management with NgRx
This guide aims on understanding why you may need local or global state managent as well as get you started with NgRx which is a library that brings Redux-like reactive state management to Angular.
1 .Why use NgRx
One of the biggest challenges with any application is managing data. In angular there are many patterns we can follow to manage your application data, and most of the time these will involve using the Input or Output decorators to share objects between components (which i know can be painfull if your application handles a lot of data) or things like RxJs Observables to listen to data changes. Now NgRx solves this problem in a reactive, ellegant way and also gives you amazing debugging features.
A quick quideline that might help you decide If you do need NgRx Store is the SHARI principle:
- Shared: state that is accessed by many components and services.
- Hydrated: state that is persisted and rehydrated from external storage.
- Available: state that needs to be available when re-entering routes.
- Retrieved: state that must be retrieved with a side-effect.
- Impacted: state that is impacted by actions from other sources.
2. How it works
Let’s look at the high level principles of NgRx: Let’s say we have a user interface that is displaying some data, then the user performs some action that shall change the state of that data, in redux the only way to change the state of the data is by dispatching an action, once an action has been dispatched it goes through a reducer function which will copy the current state of the object along with any data changes into a new object, because the state is imutable meaning it can’t be changed directly it has to be copied over to a new state, after the reducer has created the new state it gets saved in a data store which can be thought as a client side database, then in NgRx we treat this store as an observable where we can subscribe to it from anywhere in our application this means that all if our components and services will be sharing the same data at any given point and time, the reason for this is it gives us a predictable tree of state changes which will later come in handy when we want to debbug or test our application.
Key NgRx Concepts
- Actions describe unique events that are dispatched from components and services.
- State changes are handled by pure functions called reducers that take the current state and the latest action to compute a new state.
- Selectors are pure functions used to select, derive and compose pieces of state.
- State is accessed with the
Store, an observable of state and an observer of actions.
Flow of application state in NgRx
3. Getting started
In our angular project we need to install the NgRx store lib using your prefered package manager i’ll use npm in this case:
npm install @ngrx/store — save
We’re going to start with a pretty simple example where our entire application state is represented by this object wich holds information about a product in our app. Then we’ll create two different actions to either add or remove the Product from ngrx/store.
Let’s first create a dedicated model for our objects:
Since our app has multiples data objects (products, costumers etc) we create this interface with a generic type infered so that we can add any object to the ngrx/store.
Then create a file called object.actions.ts
Here we’ll export constants which represent each action (in all caps by convention)
For each class reperesenting the action well implement the Action interface, then create a readonly type that corresponds the action constant that we defined in all caps. To send an object with this action we have to define a constructor which has the payload property in this case is of type object, but could be any other type depending on the purpose.
The reset action has no constructor since we’re not trying to send any data with this action, only reset the state to the default app state. Then we export all this actions as a single type so they can be used for strong typing in other files
Let’s then build our reducer function.
The purpose of this function is to take the current state and pass it to the new state based on the changes the action is trying to make,
The default state is will be the initial state of our application, then whenever we want to create a new state we pass the newState function which uses the Object.assign to pass first an empty object, then the state and then any new data, this is because the state object is imutable and we cannot just add properties to the object itself.
Now in the reducer function we pass in the state as well as the function, and using the switch statement execute the action based on the action type we send to the reducer function.
Then in our app component:
We import the StoreModule form ngrx/store and the reducer function, we also import the StoreDevtoolsModule which we will see after why, after that on the imports metadata let’s add the ObjectReducer function.
Now for our component:
Here we handle the button actions from our html component to trigger/dispatch the actions we need in this case add/remove the object to the ngrx/store and access the object at any component or service of our application
Then inside any component or service we have acess to the state object by subscribing to the observable incomingObject we defined in our GenericComponent.ts file
Now whenever we click the add or remove button our state changes. We can console.log() our app to see what’s happening.
An even better way to see the state changes in our app is to use a chrome plugin called Redux DevTools, you enable it by installing the npm package: npm install @ngrx/store-devtools — save and then installing the chrome plugin from the chrome store, then import it to the app.module.ts and you can set the number of app states it can store at a time (refer to the app.module.ts file above), if you’ve never used it you’ll fall in love with it since it gives you debugging superpowers.
In this tutorial, we have engaged in a quick way to build a manage the state of our application with NgRx, there’s a lot more to NgRx (NgRx effects, Entities etc) which i will be covering in future posts.