State Management in Angular Using NgRx

Wednesday, March 13, 2024

State management is a concept that means managing the user control states of the applications. When the states are managed properly, they can help developers create large-scale applications with heavy data communication easily. With state management, Angular development service providers can have an application with sustainable performance. In Angular app development, the two best ways to manage the state of the user controls are by using libraries like NgRx and NGXS. Both these features are unique, but the widely used concept is NgRx. 

This is why, in this blog, we will learn about NgRx, its core pillars, and the steps to create an Angular application using NgRx.

1. State Management in Frontend Applications

In frontend applications, a proper mechanism is required to manage the application data as it can be anything from a server response or a form input. This is why the majority of the front-end developers make a practice of creating an application’s state in a central store to manage the state easily. 

2. What is NgRx?

In Angular, NgRx is a popular group of Angular libraries for state management and reactive extensions. It is an approach that makes Angular development easier by simplifying the state of the application and enforcing unidirectional data flow. In this case, a complete state management pattern enables Angular developers to have a state mode. For instance, the Angular app developers can easily create a representation of what the Angular state will look like, monitor it, change its value, and retrieve the state’s value.

In Angular app development, the NgRx package comes with libraries like –

  • RouterStore
  • Effects
  • Store
  • Entity
  • ComponentStore

3. Simple Angular Application Using NgRx State Management Library

Step 1. Install the NgRx Library

As a prerequisite to creating reactive apps in Angular, install the NgRx library and add it to your project.

Step 2: Creating the Store

1. Define the State and Initial Values

The state in NgRx shows the recent data of your application in a snapshot. This app state is held by a single immutable object. You can use the pure functions called reducers to manipulate the state and implement some changes or actions. When you start or load the app for the first time, the starting values of the state are its initial values. 

State: The state is a plain JS-based object in NgRx. It can show you complete app data for any given time. When working with a complex application, the state is broken down into small sections or features. Every section will have its reducers to handle them. 

Initial Values: Whenever the app starts or a module/feature is initialized, default values are assigned to the state properties. These default values are also known as the initial values. The initial value defines the initial state of an app before the implementation of any actions or changes. As the default parameter is defined in the reducer function, initial values are defined in a reducer. 

Create a file src/app/state/app.state.ts:

export interface AppState {
  items: string[];
}

export const initialState: AppState = {
  items: []
};

2. Define Actions

Your app’s distinct occurrences and events are represented by plain JS-based objects in NgRx called actions. Whenever any changes are made to the state of the application, actions are immediately dispatched to inform the store. They act as a medium for communication for specific events happening in the app like network requests, user interactions, and more. 

Action Definition: The constant string that identifies the type of action distinctively is defined as an action. For better reusability and organization, it is saved in a separate file. In addition to informing about the occurrence of the events, Actions also provide data that triggered the events. The carrier of this data is known as the payload. 

Action Creation: As per their name, Action creator functions create Actions. They do it by returning an action object. Action creator functions work with certain parameters regarding adding dynamic data to the action payload. 

Dispatching Actions: The Action is sent to the NgRx store by dispatching it using the store.dispatch() method. Based on different user interactions and events, the effects, services, and components can also dispatch actions. 

Handling Actions in Reducers: When the actions are dispatched, the app’s state changes in response. Reducers are the pure functions that specify those changes. In reducer, every action type already has a corresponding case that determines the updates of the state upon receiving the action. 

Create a file src/app/state/items.actions.ts:

import { createAction, props } from '@ngrx/store';

export const addItem = createAction(
  '[Items] Add Item',
  props()
);

export const removeItem = createAction(
  '[Items] Remove Item',
  props()
);

3. Define Reducer

As we saw, reducers in NgRx specify how in response to the dispatched actions should the application state change. They take in the action and the current state as arguments and return to a new state. In addition to handling transitions between different states of an app, reducers also play a crucial role in the Redux pattern. 

Reducer function: A pure function working on two parameters, i.e. an action and the current state. Based on the action type and payload, the reducer returns a new state. Instead of modifying the existing state, the reducer function creates a new state object. 

Handling actions: To specify how to manage different types of action, reducers utilize a series of if statements and a switch statement. Reducers define the corresponding case for every action type on how to update the state whenever it receives a certain action. 

Immutability: In place of modifying, the creation of a new state object helps reducers maintain their immutability.  It helps with debugging, ensures predictability, and is important for identifying the state changes in NgRx efficiently. 

Default case: In case the reducer fails to recognize the action type, a default case is included to return the current state without any changes. 

Create a file src/app/state/items.reducer.ts:

import { createReducer, on } from '@ngrx/store';
import * as ItemsActions from './items.actions';
import { AppState, initialState } from './app.state';

const _itemsReducer = createReducer(
  initialState,
  on(ItemsActions.addItem, (state, { item }) => ({
    ...state,
    items: [...state.items, item]
  })),
  on(ItemsActions.removeItem, (state, { item }) => ({
    ...state,
    items: state.items.filter(i => i !== item)
  }))
);

export function itemsReducer(state: AppState | undefined, action: Action) {
  return _itemsReducer(state, action);
}

Import statements: The @ngrx/store offers on and createReducer functions that help in defining the reducer logic. The actions that are related to the items but are more likely defined in a separate file have an alias ItemsActions. For the state properties, initialstate offers the initial values and AppState is an interface that defines its shape. Both of these functions are imported from ./app.state

Reducer Logic: The createReducer function is used to create the _itemreducer function. The series of on functions and initialState are the two arguments that determine the state changes when responding to specific actions. 

The first on function manages the addItem action as it takes the current state. The item is extracted from the action payload using the on function. It then returns a new state with an item added to its items array. Meanwhile, the removeItem action is handled using a second on function. The specified item in the items array of the state is removed using it. 

Exported Reducer Function: The itemsReducer is used and exported as the actual reducer function used in the NgRx store. There are two parameters for it: state (of type AppState | undefined) and action (of type Action). It calls the _itemReducer function with the rendered state and action and then returns the result. 
The given code shows the reducer handling items in an app state. There are only two actions (addItem and removeItem) to which the reducer listens. According to them, the reducer makes changes and updates the state. It creates a new state object for every action ensuring to maintain its immutability. The itermReducer function is exported to the NgRx store where it helps in managing the state related to the items of your Angular application.

4. Define Selectors

The functions that can offer access to the specific pieces of an app state are selectors in NgRx. When extracting data from the store, they are used to enclose the logic. You can also use the selectors to execute state transformation or derive computed values before presenting them to the components. They play a vital role in data retrieval from the store. The concerns in your app can be kept separated, thanks to the selectors. 

Selector Function: The createSelector function in the @ngrx/store builds the selector function. For that, they need a transformation function and one or more input selectors. The input selectors define the slices of the state that is going to be used whereas the transformation function takes the selected state as a reference to compute the final results.

Accessing State: By taking it as an argument, selectors can access the app state. It can be the state of a particular module or feature or it can be the state of the entire application. It depends on the purpose of the selector. 

Efficiency and Memoization: As long as the input selectors don’t change, the result of the selector keeps getting cached. This makes memoization possible as it also avoids running unnecessary computations when the state remains unchanged. 

Usage in Components: The specific slices of the state are subscribed by components using the selectors. So, when the relevant part of the state changes, they are notified immediately. This also helps reduce unnecessary rendering. 

Create a file src/app/state/items.selectors.ts:

import { createSelector } from '@ngrx/store';
import { AppState } from './app.state';

export const selectItems = (state: AppState) => state.items;

Step 3. Registering the NgRx Store

We will use the StoreModule from NgRx to register the reducer in an Angular module. 

In your AppModule (src/app/app.module.ts), add:

import { StoreModule } from '@ngrx/store';
import { itemsReducer } from './state/items.reducer';

@NgModule({
  imports: [
    // ... other imports
    StoreModule.forRoot({ items: itemsReducer })
  ],
  // ... other metadata
})
export class AppModule { }

The code given above is for the configuration of your angular app’s root module. This will allow you to use the StoreModule from NgRx to register the itemsReducer. It also enables you to handle the app state with selectors, reducers, and actions offered from NgRx as this code sets up the Redux store for state management in the Angular application. 

Step 4. Using the NgRx Store

The code given below shows how you can use the ItemsComponent, an Angular component’s NgRx store. 

In src/app/items/items.component.ts, inject the store and use it:

import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { addItem, removeItem } from '../state/items.actions';
import { selectItems } from '../state/items.selectors';
import { Observable } from 'rxjs';
import { AppState } from '../state/app.state';

@Component({
  selector: 'app-items',
  templateUrl: './items.component.html',
  styleUrls: ['./items.component.css']
})
export class ItemsComponent {
  items$: Observable;

  constructor(private store: Store) {
    this.items$ = store.select(selectItems);
  }

  add(item: string) {
    this.store.dispatch(addItem({ item }));
  }

  remove(item: string) {
    this.store.dispatch(removeItem({ item }));
  }
}

Import Statements: The Angular and NgRx libraries provide the AppState, Observable, Store, and Component which are required to type the state of the application, work with observables, use NgRx store, and define the component. 

Component Definition: The decorator called @Component helps with defining the component’s metadata. It would specify the style, template, and selector for the Angular component. 

Store Injection: An instance of the Store service is received by the component’s constructor as the dependency injection. It’s the NgRx that offers the Store service as it allows you to interact with the Redux Store. 

The app states’ item slices are easy to observe using NgRx store ItemsComponent. When it dispatches actions in the form of addition or removal of the items, interactions with the store become possible. 

Developers use this method to make sure that the angular component remains reactive to the changes happening in the application state. Empowered with a Redux-based architecture, the app will adhere to the unidirectional data flow principles. 

The code below shows a small section of the ItemsComponent template 

In src/app/items/items.component.html



  • {{ item }}

4. Conclusion

As seen in this code, NgRx is one of the most important libraries that is used by Angular app development companies for state management in web applications. With this blog and its examples, you can learn how to create your Angular application using NgRx.

FAQ:

1. What is state management in Angular?

In Angular, state management is an essential process that enables the developers to manage the states of user controls. It also helps in creating large-scale Angular apps that require heavy data communication. 

2. What is the best way to manage the state in Angular?

In the Angular ecosystem, the best way to manage the state of an application is with the use of Angular services. Developers can create and inject a service into components while centralizing the state logic and making it available for multiple components. 

3. How to use NgRx state management in Angular?

To use NgRx state management in Angular, the developers should consider the following things- 

  • Create an Angular application with the use of Angular CLI.
  • Load the application into the Angular IDE and run it.
  • Install the required tools and NgRx.
  • In the application, add an NgRx store.
  • Create a customer model and a sub-module for it.
  • Add actions at the last. 

Comments


Your comment is awaiting moderation.