{"id":11313,"date":"2021-12-16T17:57:58","date_gmt":"2021-12-16T17:57:58","guid":{"rendered":"https:\/\/www.htmlgoodies.com\/?p=11313"},"modified":"2021-12-16T17:57:58","modified_gmt":"2021-12-16T17:57:58","slug":"angular-state-management-with-ngrx","status":"publish","type":"post","link":"https:\/\/www.htmlgoodies.com\/javascript\/angular-state-management-with-ngrx\/","title":{"rendered":"Angular State Management with NgRx"},"content":{"rendered":"
Angular applications do not have any quick way to achieve data persistence. As a result, the experience of developing large applications that require a lot of data communication among components can be very hectic. State management is perhaps the hardest part to manage in any large-scale application.<\/p>\n
In this article, we will discuss the NgRx package<\/a> and how it is used for state management and data communication in an Angular application. NgRx consists of many libraries that help the developers to manage the state in a large-scale, modern client-side Angular application.<\/p>\n In backend applications, we can use databases for state management. However, in the case of front-end applications, we also need some mechanism to store data. This data can be something like a response received from a server, form input data, or something else entirely.<\/p>\n The idea of reactive state management is to store all of the application state in a central store for data communication. So, in the context of state management, we have our state<\/i>, which would be responsible for representing the application that actually lives in the store.<\/p>\n NgRx is a group of libraries that provide reactive state management in an application. A good state management system should enable you to give a representation of the state, update or modify its value, keep track of the state when the value changes, and, finally, retrieve the state. Inspired by the Redux pattern, NgRx provides a way for easily simplifying the application\u2019s state and carrying out the unidirectional data flow in the application. The NgRx package is composed of the following libraries:<\/p>\n Read:<\/b> Working with Redux for State Management<\/a><\/p>\n NgRx uses the Redux pattern. It consists mainly of three models: <\/p>\n In this section, we will see how the elements of NgRx – such as store, actions, and reducers – are used to simplify the process of state management in Angular applications.<\/p>\n Store plays a vital role in state management. Store holds the data which we would be using in our app.<\/p>\n Here is an example of a store in NgRX:<\/p>\n NgRx actions are the methods dispatched by the component or service when an event is called. Here is an example of an NgRx action:<\/p>\n You can define the type and payload in the action<\/b> method.<\/p>\n NgRx reducers are the functions that handle the state transitions. It receives the current state and an action as arguments and returns a new state. To get a better understanding of reducers, consider the following code:<\/p>\n Here, createReducer<\/b> function is responsible for handling the state transitions. We have also imported the initial state into our reducer file to access the current state. After that, the on<\/b> event is used, which accepts the name of the action as a parameter and is used for triggering the action.<\/p>\n Read:<\/strong> How to Optimize Angular Applications<\/a><\/p>\n Create a new Angular app by running the following command on your terminal:<\/p>\n Next, Install the NgRx store in your project:<\/p>\n Now, open the application in VSCode (or any code editor or IDE which you like to use) and create a new folder called store<\/b> inside the src\/app<\/b> directory.<\/p>\n Inside the store directory, create a models folder and define an interface there for country-list<\/b>:<\/p>\n Next, create an Action<\/b> directory inside the store<\/b> folder. This is where we will define all the NgRx actions. So create a country.action.ts<\/b> file inside the actions directory and add the following code to it:<\/p>\n In the next step, we will create a reducers<\/b> directory inside the store<\/b> directory. In the reducers<\/b> directory, we will create a country.reducer.ts<\/b> file and add the following code to it:<\/p>\n Recall that the objective of any state management system is to keep all the state of an application in a single store<\/b> so that it can be easily accessed from any part of the application. Now, we will create a file named state.model.ts<\/b> inside the model directory and create an AppState<\/b> interface using the following code:<\/p>\n Now, we need to register NgRx into our root module file app.modules.ts<\/b>:<\/p>\n Next, we will register the imported modules in the imports array:<\/p>\n NgRx is now ready to use in our components. So let\u2019s modify our root component file app.component.ts<\/b> so that we can use NgRx there:<\/p>\n Here, we have defined the countryItem<\/b> interface, brought in an RxJS observable, and our app state. We have also set countryItem$<\/b> to a type of observable, which is of type array<\/i>, and, finally, set the value of countryItem$<\/b> observable to the returned state. We will use this information in our our template file app.component.html:<\/b><\/p>\n In the next step, we will create a user form for adding countries to the country list. In this project, we are using Bootstrap for a nice user-experience:<\/p>\n To bring the Bootstrap into our project, we have added Bootstrap CSS CDN<\/b> to app\/index.html file<\/b>.<\/p>\n In the next step, we have to modify the app.component.html<\/b> file accordingly so that the user can add the country name and short name to the list of countries.<\/p>\n Next, we need to import the NgForm in our app.component.ts<\/b> file and create a method to dispatch the AddItemAction<\/b>:<\/p>\n Here is a screenshot of the final result:<\/p>\n <\/p>\n You can find the source code from this article on Github.<\/a><\/p>\nManaging the State of Front-end Applications<\/h2>\n
What is NgRx?<\/h2>\n
\n
NgRx and Redux<\/h3>\n
\nStore<\/b>: A central point that holds all the states of an application
\nAction<\/b>: The unique events describing changes in the state of an application
\nReducer<\/b>: The functions used for carrying out the state transitions by bonding the store and actions together<\/p>\nFundamental Elements of NgRx<\/h2>\n
NgRx Store<\/h3>\n
const state = {\r\n persons: [\r\n {\r\n name: \"Adam\",\r\n age: 45\r\n }, {\r\n name: \"George\",\r\n age: 65\r\n }\r\n ],\r\n bookDescription: {\r\n name: \"Name of Book\",\r\n author: \"Kate Chopin\"\r\n }\r\n}\r\n \r\n<\/pre>\n
NgRx Actions<\/h3>\n
const NameModifyAction = {\r\n type: \"Name Modify\",\r\n name: \"Kate\"\r\n}\r\n<\/pre>\n
NgRx Reducers<\/h3>\n
const _reducer = createReducer(\r\n initialState,\r\n on(nameOfAction, (state, action) => {\r\n return {\r\n ...state,\r\n someState: action.anyState\r\n }\r\n })\r\n)\r\n<\/pre>\n
Building a Simple Angular App Using NgRx<\/h2>\n
ng new country-list \r\n<\/pre>\n
npm install @ngrx\/store \u2013-save\r\n<\/pre>\n
export interface CountryItem {\r\n id: string;\r\n name: string;\r\n shortName: string;\r\n}\r\n<\/pre>\n
import { Action } from '@ngrx\/store';\r\nimport { CountryItem } from '..\/models\/countryItem.model';\r\nexport enum CountryActionType {\r\n ADD_ITEM = '[COUNTRY] Add Country',\r\n}\r\nexport class AddItemAction implements Action {\r\n readonly type = CountryActionType.ADD_ITEM;\r\n \/\/optional payload\r\n constructor(public payload: CountryItem) {}\r\n}\r\nexport type CountryAction = AddItemAction;\r\n<\/pre>\n
import { CountryItem } from '..\/models\/countryItem.model';\r\nimport { CountryAction, CountryActionType } from '..\/actions\/country.action';\r\n\/\/ \/\/create a dummy initial state\r\n \r\nconst initialState: Array = [\r\n {\r\n id: '1',\r\n name: 'United States of America',\r\n shortName: 'US',\r\n },\r\n];\r\n \r\nexport function CountryReducer(\r\n state: Array = initialState,\r\n action: CountryAction\r\n) {\r\n switch (action.type) {\r\n case CountryActionType.ADD_ITEM:\r\n return [...state, action.payload];\r\n default:\r\n return state;\r\n }\r\n}\r\n<\/pre>\n
import { CountryItem } from '.\/countryItem.model';\r\nexport interface AppState {\r\n readonly country: Array;\r\n}\r\n<\/pre>\n
import { CountryReducer } from '.\/store\/reducers\/country.reducer';\r\nimport { FormsModule } from '@angular\/forms';\r\n<\/pre>\n
imports: [\r\n FormsModule,\r\n StoreModule.forRoot({\r\n country: CountryReducer,\r\n }),\r\n ]\r\n<\/pre>\n
import { Store } from '@ngrx\/store';\r\nimport { Observable } from 'rxjs';\r\nimport { CountryItem } from '.\/store\/models\/countryItem.model';\r\nimport { AppState } from '.\/store\/models\/app-state.model';\r\nimport { NgForm } from '@angular\/forms';\r\nimport { AddItemAction } from '.\/store\/actions\/country.action';\r\n@Component({\r\n selector: 'app-root',\r\n templateUrl: '.\/app.component.html',\r\n styleUrls: ['.\/app.component.css'],\r\n})\r\nexport class AppComponent implements OnInit {\r\n countryItems$: Observable<Array>;\r\n constructor(private store: Store) {}\r\n ngOnInit(): void {\r\n this.countryItems$ = this.store.select((store) => store.country);\r\n }\r\n}\r\n<\/pre>\n
<div class=\"col-md-6 py-4\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<ul\u00a0class=\"list-group\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<li\u00a0class=\"list-group-item\"\u00a0*ngFor=\"let\u00a0country\u00a0of\u00a0countryItems$\u00a0|\u00a0async\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{{country.name}}:\u00a0<b>{{country.shortName}}<\/b><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/li><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/ul><\/span>\r\n<\/div><\/span><\/pre>\n
<!doctype html><\/span>\r\n<html lang=\"en\"><\/span>\r\n<head><\/span>\r\n \u00a0<meta charset=\"utf-8\"><\/span>\r\n \u00a0<title>Angular\u00a0State\u00a0Management\u00a0Demonstration<\/title><\/span>\r\n \u00a0<base href=\"\/\"><\/span>\r\n \u00a0<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><\/span>\r\n \u00a0<link rel=\"icon\" type=\"image\/x-icon\" href=\"favicon.ico\"><\/span>\r\n \u00a0<link rel=\"stylesheet\" href=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@4.6.0\/dist\/css\/bootstrap.min.css\"<\/span>\r\n \u00a0\u00a0\u00a0integrity=\"sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L\/Z6nronJ3oUOFUFpCjEUQouq2+l\" crossorigin=\"anonymous\"><\/span>\r\n<\/head><\/span>\r\n<body><\/span>\r\n \u00a0<app-root><\/app-root><\/span>\r\n<\/body><\/span>\r\n<\/html><\/span><\/pre>\n
<section><\/span>\r\n \u00a0<div\u00a0class=\"container\"><\/span>\r\n \u00a0\u00a0\u00a0<div\u00a0class=\"row\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0<div\u00a0class=\"col-md-6\u00a0px-4\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<h4>Angular\u00a0State\u00a0Management\u00a0using\u00a0NgRx<\/h4><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0<\/div><\/span>\r\n \u00a0\u00a0\u00a0<\/div><\/span>\r\n \u00a0\u00a0\u00a0<div\u00a0class=\"row\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0<div\u00a0class=\"col-md-6\u00a0py-4\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<div\u00a0class=\"card\u00a0p-4\u00a0shadow-sm\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<form\u00a0#myform=\"ngForm\"\u00a0(ngSubmit)=\"addCountry(myform)\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<div\u00a0class=\"form-group\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<label\u00a0for=\"name\">Identity<\/label><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<input\u00a0type=\"text\"\u00a0class=\"form-control\"\u00a0ngModel\u00a0name=\"id\"\u00a0id=\"id\"\u00a0aria-describedby=\"name\"\u00a0required><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/div><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<div\u00a0class=\"form-group\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<label\u00a0for=\"name\">Country Name<\/label><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<input\u00a0type=\"text\"\u00a0class=\"form-control\"\u00a0ngModel\u00a0name=\"name\"\u00a0id=\"name\"\u00a0aria-describedby=\"name\"\u00a0required><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/div><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<div\u00a0class=\"form-group\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<label\u00a0for=\"department\">Short\u00a0Name<\/label><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<input\u00a0type=\"text\"\u00a0class=\"form-control\"\u00a0ngModel\u00a0name=\"shortName\"\u00a0id=\"shortName\"\u00a0required><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/div><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<button\u00a0type=\"submit\"\u00a0class=\"btn\u00a0btn-primary\">Submit<\/button><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/form><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/div><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0<\/div><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0<div\u00a0class=\"col-md-6\u00a0py-4\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<ul\u00a0class=\"list-group\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<li\u00a0class=\"list-group-item\"\u00a0*ngFor=\"let\u00a0country\u00a0of\u00a0countryItems$\u00a0|\u00a0async\"><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{{country.name}}:\u00a0<b>{{country.shortName}}<\/b><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/li><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<\/ul><\/span>\r\n \u00a0\u00a0\u00a0\u00a0\u00a0<\/div><\/span>\r\n \u00a0\u00a0\u00a0<\/div><\/span>\r\n \u00a0<\/div><\/span>\r\n<\/section><\/span><\/pre>\n
export class AppComponent implements OnInit {\r\n countryItems$: Observable<Array>;\r\n constructor(private store: Store) {}\r\n \t\t\t...\r\n addCountry(form: NgForm) {\r\n this.store.dispatch(new AddItemAction(form.value));\r\n form.reset();\r\n }\r\n}\r\n<\/pre>\n