This article is for angular developers starting with NgRx. This will help you understand the core concepts of state management system and how you can implement them using NgRx in angular. Since this is a beginner level guide I will using simple language and examples shown will be very basic but you can easily build on those after understanding the core idea on how to work with NgRx.
I will be explaining some theory first but if you want start from the sample code and then move up the article.
prerequisites to better understand this article
- Working knowledge of Angular.
- Understanding of RxJS operators, Observables etc.
What is State Management & Why is it required?
When working in large angular projects you may need to maintain data across the application that could decide how you application behaves. Lets say based on user roles in some component you need to hide something and that component is 6 7 levels inside the root component.
Passing Data through Input/Output decorators one parent to child component at a time becomes tedious in such scenarios. You can make use of Angular Services clubbed with Subjects to handle these scenarios but using frameworks like NgRx standardizes many things and that, has its own advantages. Initial setup for NgRx may seem cumbersome but adding on to that is pretty easy.
Initially we will get some jargons out of the way and then we will dive into the code.
- Store: The NgRx Store can be though of as the single place that has all our application data. Whenever we need information we will ask the store for it. Similarly, any event that modifies data should modify the data in the store. You can think of it as a huge object at root level.
- Actions: These are unique identifiers to any event that might happen in our application. We can have an action for a updating loggedIn State with id as ‘[App] update login state’. using such actions we update data in the NgRx store.
- Reducers: These are functions that listen to events and based on the action update the data in the store in an immutable way. we always return a new state of the data we are updating in the store, never modify the existing data/object.
- Selectors: To extract data we require from the store, selectors are used to drill down the nested objects inside the store.
- Effects: To handle Async operations like api calls or any operation that we need to perform before the reducer actually updated the store we use effects.
It is not mandatory to use NgRx Forms when using NgRx to manage state but it helps in providing an integrated way of handling forms as part of the application state. Also, it is based on Angular Forms so there will be similarities (like the object of form control will have value, dirty, pristine, touched etc.)
There are functions to create FormGroups and we attach them to the main state in our reducer so that everything is a part of the store.
Let’s dive into sample app & Code
Our application is pretty basic, a simple input field attached to NgRx forms. On button click whatever is there in the input field will be updated in the store and shown on the screen. The input field is in the app component and data shown is inside child component (here its just one level down but in reality this component can be anywhere in the application and same code would still work)
The key players we mentioned above will all be in separate files, you can create a new folder called store to keep these files
Install the following packages using npm install
- npm install — save @ngrx/store
- npm install — save @ngrx/effects
- npm install — save ngrx-forms
- npm install @ngrx/store-devtools (for debugging store using chrome extension, we will cover that later in the article)
- createAction method is used to create an action
- specify a unique Id and if you want to pass any data define it within props
- we will only pass a simple string in payload property you can have any type of object you want to pass
- actionInterface is just to define the type of data
Let’s go through the code line by line
- check out the imports we will using in this file
- we define unique id for our form, FORM_ID
- we provide any initial data we want the form to have (this is why the textbox has initialState written when application loads)
- using updateGroup you can add validations
- for more info on types of validations and ngrx forms go to below link
Introduction - ngrx-forms
This user guide explains and showcases all of the different features of ngrx-forms in detail. Use the navigation menu…
- Define interfaces for the store data
- message value of ‘I am a default message’ is seen below the textbox when app loads
- In Store interface the key ‘childMessage’ is what we use in app module to register the reducer, hence all data inside this reducer would be nested inside childMessage key.
- Here StoreModule is to register the reducers
- EffectsModule registers the effects ( we will look into it later in the article)
- StoreDevTools is for the chrome extension to view the store in realtime
- NgrxFormsModule is required for using ngrx forms
- If you dont need to perform form validations the above method is all you need
- We use the createReducer method which takes the default/initial value for the reducer ‘childMessage’ that we registered in the store in app.module.ts
- Now using ‘on’ we can write reducer logic for respective actions (defined in the app.actions.ts file). The callback gets the current state & any props that we pass while dispatching values. We then change the state in immutable way and return the new state
- For now lets just check the dispatch logic
- using the store that we got through angular data injection we can use the dispatch method to update the store via reducers based on a particular action.
- In this case we are also passing the props we defined in the app.action.ts file for changeMessageAction
- How this value reflects on UI we will discuss in selectors section below
- for registering validations we need to use this reducer as a wrapper
- wrapReducerWithFormStateUpdate take the original reducer as first argument, then we have a callback to return the form from the store
- the 3rd argument is the validationReducer that we created using updateGroup in the same file above.
- this reducer gets execute at the end and runs the validations we gave in updateGroup on respective formControls.
- We use selectors to drill down the nested object structure of our store. after discussing the methods we will see the complete store object to make it more clear.
- createFeatureSelector gets the object from the root level, after that we can pass the selectors as arguments to createSelector and drill down one level at a time.
- you can see the store object structure in above image and relate to the selectors to make the drilling down process clearer.
- To use this extension, get it from chrome webstore by searching for redux dev toolsand install it
- npm install @ngrx/store-devtools
- now import it in module
- Now in chrome dev tools you will find a redux tab where you can do a lot of stuff
- To make use of selectors get the injected store and use select function. pass the relevant selector to get an observable that will return the relevant data on being subscribed
- Here this.message is of type observable and if you want to follow the standard you can also name observables with $ sign like message$
- Now you can use async pipe to get this data in html
- It will keep subscribing to changes, so whenever there is a dispatch from some other component that changes the message, value will reflect on the UI.
In this case we just show a demo by adding data to localStorage in the effect before forwarding the dispatch to final change action.
- we make use of the createEffect Method
- In callback we use the actions$ observable to subscribe to all actions dispatched
- ofType is used to filter from all actions so that we only apply the logic to the specific action that is intercepted by the effect.
- We don’t use the store$ here but you can refer the code in case you need access to the store data for some logic in the effect
- withLatestFrom just clubs the two observables, you can check RxJS documentation or just google for lots of examples
- Once our side effect (storing to localStorage in this dummy example) is complete we then forward the request to a reducer to update the store
- if we don’t want to update the store state we have to use dispatch false option
The code inside ngOnInit is just to listen to changes in our input textbox and update the this.msg variable which gets dispatched as props and finally updates the message property inside the state of the store.
To see how to register the Form in Dom refer below code snippet
- we use the formState and register using the ngrxFormState directive
- For Form controls make use of ngrxFormControlState directive
We have just scratched the surface but the above information is enough to get you started with basic applications and it will give you a launchpad from where you can progress and understand the more complex concepts as well. On a side note I feel being comfortable with RxJS will also help you a lot.